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.

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.
parallelThe 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.
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.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.
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.
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.
whenA 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:
| Condition | Purpose | Example |
|---|---|---|
| branch | Runs only on a specific branch | branch 'main' |
| environment | Runs if an environment variable matches | environment name: 'ENV', value: 'production' |
| expression | Any free Groovy expression | expression { return params.DEPLOY } |
| changeset | Runs if certain files changed | changeset '**/*.java' |
| buildingTag | Runs if the build was triggered by a tag | buildingTag |
when { branch 'main' } limits deploys to the main branch only. Combining several conditions in one when block is AND — all must be met:
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.
postAfter 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.
All concepts combined: parallel tests, conditional builds, conditional deploys, and a self-cleaning post block:
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.
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!