Jenkins Scripted Pipeline with Groovy

Scripted Pipeline gives you full Groovy programming power inside Jenkins. While Declarative Pipeline uses a fixed structure, Scripted Pipeline lets you write logic, loops, and conditions using code. This makes it suitable for complex, dynamic workflows.

Think of Declarative Pipeline as a pre-designed house with fixed rooms in fixed positions. Scripted Pipeline is an open plot of land where you design the entire floor plan yourself. More freedom, but more responsibility.

The node Block

Every Scripted Pipeline starts with a node block. The node block allocates a build executor on a Jenkins agent and runs all the code inside it.

// Run on any available agent
node {
    stage('Build') {
        sh 'mvn clean package'
    }
}

// Run on an agent with a specific label
node('linux-builder') {
    stage('Build') {
        sh 'mvn clean package'
    }
}

Stages in Scripted Pipeline

Stages in Scripted Pipeline are created with the stage function. They appear in the Jenkins stage view, just like in Declarative Pipeline. The difference is that stages are not enforced by structure — you can place them anywhere, nest them, or create them dynamically.

node {
    stage('Checkout') {
        git branch: 'main',
            url: 'https://github.com/myteam/myapp.git'
    }

    stage('Build') {
        sh 'mvn clean compile'
    }

    stage('Test') {
        sh 'mvn test'
    }

    stage('Package') {
        sh 'mvn package -DskipTests'
        archiveArtifacts 'target/*.jar'
    }
}

Variables and Groovy Expressions

In Scripted Pipeline, you use standard Groovy to define and work with variables.

node {
    def appName    = 'payment-service'
    def buildDate  = new Date().format('yyyyMMdd')
    def version    = "1.0.${env.BUILD_NUMBER}"
    def deployHost = env.BRANCH_NAME == 'main' ? 'prod-server' : 'staging-server'

    stage('Info') {
        echo "App: ${appName} | Version: ${version} | Date: ${buildDate}"
        echo "Will deploy to: ${deployHost}"
    }

    stage('Build') {
        sh "mvn clean package -Dapp.version=${version}"
    }
}

Conditional Logic

Use standard Groovy if/else statements to control the pipeline flow.

node {
    stage('Build') {
        sh 'mvn clean package'
    }

    stage('Deploy') {
        if (env.BRANCH_NAME == 'main') {
            echo 'Deploying to PRODUCTION'
            sh 'deploy.sh production'
        } else if (env.BRANCH_NAME == 'develop') {
            echo 'Deploying to STAGING'
            sh 'deploy.sh staging'
        } else {
            echo "Branch ${env.BRANCH_NAME} — no deployment configured"
        }
    }
}

Loops in Scripted Pipeline

Loops are not possible in Declarative Pipeline. In Scripted Pipeline, you use standard Groovy loops to perform repeated operations.

node {
    // Deploy to multiple servers
    def servers = ['server-1', 'server-2', 'server-3']

    stage('Deploy to All Servers') {
        for (server in servers) {
            stage("Deploy to ${server}") {
                echo "Deploying to ${server}..."
                sh "scp target/myapp.jar deploy@${server}:/opt/app/"
                sh "ssh deploy@${server} 'systemctl restart myapp'"
            }
        }
    }
}
Diagram: Dynamic stage creation

servers = ['server-1', 'server-2', 'server-3']

Pipeline Stages Generated:
  Checkout
  Build
  Test
  Deploy to server-1  ← Created by loop
  Deploy to server-2  ← Created by loop
  Deploy to server-3  ← Created by loop

Try-Catch for Error Handling

Scripted Pipeline handles errors using try-catch blocks — the standard Groovy error handling approach. This gives you precise control over what happens when a step fails.

node {
    stage('Build') {
        try {
            sh 'mvn clean package'
            echo 'Build succeeded'
        } catch (Exception e) {
            echo "Build failed with error: ${e.getMessage()}"
            currentBuild.result = 'FAILURE'
            throw e  // Re-throw to mark the build as failed
        } finally {
            // This runs whether the build passed or failed
            junit 'target/surefire-reports/*.xml'
            cleanWs()
        }
    }
}

The finally block is equivalent to Declarative Pipeline's post { always {} } block. It runs regardless of whether the try block succeeded or threw an error.

Parallel Execution in Scripted Pipeline

Use the parallel step to run tasks simultaneously. Provide a map of stage names to closures.

node {
    stage('Build') {
        sh 'mvn clean package -DskipTests'
    }

    stage('Run Tests in Parallel') {
        parallel(
            'Unit Tests': {
                node {
                    sh 'mvn test -Dtest.type=unit'
                    junit 'target/surefire-reports/*.xml'
                }
            },
            'Integration Tests': {
                node {
                    sh 'mvn test -Dtest.type=integration'
                    junit 'target/failsafe-reports/*.xml'
                }
            },
            'Performance Tests': {
                node {
                    sh 'gatling.sh -s LoadSimulation'
                }
            }
        )
    }
}

Using the script Block Inside Declarative Pipeline

You do not need a fully Scripted Pipeline to use Groovy logic. Inside Declarative Pipeline, wrap Groovy code in a script block. This lets you use loops, conditions, and Groovy expressions without switching to full Scripted syntax.

pipeline {
    agent any
    stages {
        stage('Smart Deploy') {
            steps {
                script {
                    // Groovy code inside Declarative Pipeline
                    def targets = ['us-east', 'eu-west', 'ap-south']
                    for (region in targets) {
                        echo "Deploying to ${region}"
                        sh "deploy.sh --region ${region}"
                    }
                }
            }
        }
    }
}

This hybrid approach is the most practical solution. Use Declarative Pipeline for structure and readability, and use script blocks for logic where needed.

Common Built-in Pipeline Steps

StepWhat It Does
sh 'command'Run a shell command (Linux/Mac)
bat 'command'Run a Windows batch command
echo 'message'Print text to the console
git url: '...', branch: '...'Check out a Git repository
archiveArtifacts 'path'Save files as build artifacts
junit 'path/*.xml'Publish test results
cleanWs()Delete the workspace
input 'message'Pause and wait for human approval
sleep 30Pause for 30 seconds
currentBuild.result = 'FAILURE'Set the build result programmatically

Key Points

  • Scripted Pipeline uses Groovy code inside a node block, giving full programming flexibility.
  • Use variables, if/else conditions, and loops to create dynamic pipelines.
  • Error handling with try-catch gives precise control over failure behavior.
  • The script block inside Declarative Pipeline lets you add Groovy logic without switching to full Scripted syntax.
  • Most teams use Declarative Pipeline with script blocks rather than fully Scripted Pipeline.

Leave a Comment