Learn Jenkins - Advanced Flow Control (Parallel, Matrix & Conditionals)
Episode 7 of 21

Learn Jenkins - Advanced Flow Control (Parallel, Matrix & Conditionals)

A pipeline that runs stage by stage sequentially wastes valuable build time. This episode dissects parallel execution, matrix strategies for multi-environment builds, conditional execution with when, and post-build actions with post so the pipeline becomes fast, smart, and structured.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Introduction

In episode 6 we built an interactive pipeline with parameters and a manual approval gate. But there is one big weakness we have not touched: every stage in our pipeline still runs sequentially. The test stage does not start before the build stage finishes, and the deploy stage waits for the tests to finish. If a build takes an hour, and most of that time could actually run in parallel, we are wasting half of it.

Imagine a restaurant kitchen with only one chef: he cooks the appetizer, then waits, then cooks the main course, then dessert — while the oven, stove, and blender sit idle. An efficient kitchen runs several dishes at once with different chefs. That is what this episode does for Jenkins: parallel execution, matrix strategies to test many environment combinations at once, conditional execution so stages only run when needed, and post-execution actions to handle every possible build outcome neatly.

Main Discussion

Parallel Execution with parallel

The parallel block runs several stages simultaneously, each on a separate executor. This is the easiest way to cut total pipeline duration: three test stages of 5 minutes each become 5 minutes total instead of 15.

JenkinsParallel stages for testing
stage('Testing Paralel') {
    failFast true
    parallel {
        stage('Unit Test') {
            steps {
                sh 'npm run test:unit'
            }
        }
        stage('Integration Test') {
            steps {
                sh 'npm run test:integration'
            }
        }
        stage('Lint & Typecheck') {
            steps {
                sh 'npm run lint'
            }
        }
    }
}

A few important rules:

  • failFast true aborts the other parallel branches as soon as one branch fails. Without it, the other branches keep running to completion — sometimes desirable (collect all failures), sometimes wasteful. Choose per need.
  • Each parallel branch gets its own copy of the workspace. File changes in one branch are not visible in another — a pipeline design that relies on a sequence of file modifications is not safe here.
  • If agents are available, branches can be directed to different labels by adding an agent inside each stage, so unit tests and integration tests run on separate machines.

Note

Parallelism is limited by the number of executors available on the agent. If Jenkins only has one executor and one agent, parallel stages will still be scheduled sequentially. Make sure you have the distributed architecture from episode 3 in place — multiple agents or dynamic agents — so parallel actually means parallel.

Matrix Strategy for Multi-Environment Builds

The matrix block runs the same stage for every combination of the axes you define. The most classic example: a Node.js app that must pass on several Node versions across several OSes. Without a matrix, you write the same build stage many times with different values; with a matrix, a single stage definition runs for all combinations.

JenkinsNode & OS matrix strategy
matrix {
    axes {
        axis {
            name 'NODE_VER'
            values '18', '20'
        }
        axis {
            name 'OS'
            values 'linux', 'windows'
        }
    }
    excludes {
        exclude {
            axis {
                name 'OS'
                values 'windows'
            }
            axis {
                name 'NODE_VER'
                values '18'
            }
        }
    }
    stages {
        stage('Install & Build') {
            steps {
                echo "Node ${NODE_VER} di ${OS}"
                sh 'npm ci'
                sh 'npm run build'
            }
        }
    }
}

How to read it: four combinations will be run — Node 18 on linux, Node 18 on windows, Node 20 on linux, Node 20 on windows. The excludes block removes unwanted combinations (for example the team does not maintain Node 18 support on windows), so only three combinations actually execute. Each axis value is accessed as a variable named after the axis — NODE_VER and OS — as interpolated in the echo line inside the code block.

Warning

The sh step does not run on Windows agents — there you must use the bat step. In real practice, stages inside a matrix often use when { expression { return OS == 'linux' } } to choose which step runs, or provide two step branches based on the axis value. Make sure the matrix combinations you allow can actually be executed by the chosen toolchain.

Conditional Execution with when

A stage does not have to always run. The when block makes a stage conditional: it only executes if the condition is met. This is what lets a single Jenkinsfile serve many flows — dev, staging, and production — without rewriting.

The conditions most often used:

ConditionPurposeExample
branchRuns only on a specific branchbranch 'main'
environmentRuns if an environment variable matchesenvironment name: 'ENV', value: 'production'
expressionAny free Groovy expressionexpression { return params.DEPLOY }
changesetRuns if certain files changedchangeset '**/*.java'
buildingTagRuns if the build was triggered by a tagbuildingTag

when { branch 'main' } limits deploys to the main branch only. Combining several conditions in one when block is AND — all must be met:

JenkinsCombined conditions
stage('Build Java') {
    when {
        changeset '**/*.java'
    }
    steps {
        sh 'mvn -q package'
    }
}
 
stage('Deploy') {
    when {
        branch 'main'
        expression { return params.DEPLOY }
    }
    steps {
        echo 'Deploying ke production'
    }
}

when { changeset '**/*.java' } is a huge optimization: the Java build stage only runs if a Java file changed in this commit. A commit that only touches the README does not trigger the slow Java compilation. Note expression { return params.DEPLOY }, which reads the boolean parameter from episode 6 — the combination of parameters and when produces a highly flexible pipeline.

Tip

Use when together with beforeAgent: true for stages that do not need an agent. Example: a stage that only computes a version or decides the path does not need to lease an executor. With when { branch 'main'; beforeAgent true }, Jenkins evaluates the condition before allocating an agent, saving time and resources.

Post-Execution Actions with post

After all stages finish, the post block determines what happens based on the final result. This is the right place to collect test reports, send notifications, or clean the workspace — not in the middle of a stage. The most common conditions:

  • always runs regardless of the result — good for publishing test reports via junit.
  • success and failure for outcome-specific actions, such as notifications.
  • unstable when some tests fail but the build did not crash.
  • aborted when the build was cancelled by a user or a timeout.
  • cleanup is special: it always runs at the end — even when a build is stopped mid-flight or fails before stages complete. The safest place to clean the workspace.

The full application can be seen in the pipeline example below: post publishes test reports, marks failures, and cleans the workspace on every build.

Complete Pipeline Example

All concepts combined: parallel tests, conditional builds, conditional deploys, and a self-cleaning post block:

JenkinsParallel + conditional + post pipeline
pipeline {
    agent any
 
    parameters {
        booleanParam(name: 'DEPLOY', defaultValue: false, description: 'Jalankan deploy?')
    }
 
    stages {
        stage('Testing') {
            failFast true
            parallel {
                stage('Unit Test') {
                    steps { sh 'npm run test:unit' }
                }
                stage('Lint') {
                    steps { sh 'npm run lint' }
                }
            }
        }
 
        stage('Build Package') {
            when {
                changeset 'src/**'
            }
            steps {
                sh 'npm run build'
            }
        }
 
        stage('Deploy Production') {
            when {
                branch 'main'
                expression { return params.DEPLOY }
            }
            steps {
                echo 'Deploy ke production'
            }
        }
    }
 
    post {
        always {
            junit 'reports/**/*.xml'
        }
        failure {
            echo 'Ada stage yang gagal'
        }
        cleanup {
            cleanWs()
        }
    }
}

Notice the flow pattern: tests run in parallel and fast, the build only triggers when source code changes, and production deploy only happens from the main branch and only if a human checked the DEPLOY parameter. The same pipeline serves developers (ordinary commits, no deploy) and production (controlled) — without duplicating the Jenkinsfile.

Conclusion

In episode 7 we dissected four flow control tools that turn a linear pipeline into a fast, smart one: parallel to run testing stages simultaneously with failFast, matrix with axes and excludes to test many environment combinations from a single definition, when for conditional execution based on branch, expression, and changeset, and post for post-build actions such as report publishing, notifications, and cleanup.

The key takeaways to carry with you:

  • parallel cuts build duration; remember each branch has its own workspace copy.
  • matrix runs a stage for every combination of axes; excludes removes unwanted combinations.
  • when makes stages conditional; combined conditions in one block are AND.
  • when { changeset '...' } only builds what actually changed — a huge build-time saver.
  • post { cleanup } always runs last, even when a build fails or is stopped.

Your pipeline is now fast and adaptive. In episode 8 we move to a much-anticipated topic: Docker integration inside a Jenkins pipeline — running the entire build inside containers, building Docker images from the pipeline, and pushing them to a registry securely. See you in episode 8!