Jenkins Declarative vs Scripted Pipeline: Syntax, Tradeoffs, and Examples

The Difference in One Sentence

Declarative Pipeline gives a Jenkinsfile a validated, opinionated structure; Scripted Pipeline exposes Jenkins Pipeline steps through a general Groovy control flow. Both ultimately run on the same Pipeline engine, but they optimize for different things.

For most application delivery pipelines, start with Declarative. Its stage model is readable in reviews, invalid placement is rejected early, and features such as post, when, options, matrix, and stage-level agents have explicit homes. Use Scripted when the workflow itself must be generated dynamically and cannot be expressed cleanly with Declarative directives.

You can practice both models in ShellGenius Jenkins Labs, beginning with Repair the First Pipeline and Repair a Scripted Pipeline.

---

The Same Delivery Flow in Declarative Syntax

pipeline {
  agent { label 'linux-docker' }

  options {
    timestamps()
    timeout(time: 30, unit: 'MINUTES')
    buildDiscarder(logRotator(numToKeepStr: '20'))
  }

  parameters {
    choice(name: 'TARGET', choices: ['staging', 'production'])
  }

  stages {
    stage('Checkout') {
      steps { checkout scm }
    }

    stage('Test') {
      steps { sh 'npm ci && npm test' }
      post { always { junit 'reports/*.xml' } }
    }

    stage('Deploy') {
      when { branch 'main' }
      steps {
        sh './deploy.sh "$TARGET"'
      }
    }
  }

  post {
    always { deleteDir() }
    failure { echo 'Delivery failed; inspect the first failed stage' }
  }
}

The structure is the feature. Jenkins can validate that stages contains stage blocks, that a stage contains one of steps, parallel, matrix, or stages, and that post conditions are in a supported location. A reviewer can find retention, timeouts, deployment gates, and cleanup without tracing arbitrary control flow.

---

The Equivalent Scripted Pipeline

def target = params.TARGET ?: 'staging'

node('linux-docker') {
  timestamps {
    timeout(time: 30, unit: 'MINUTES') {
      try {
        stage('Checkout') {
          checkout scm
        }

        stage('Test') {
          try {
            sh 'npm ci && npm test'
          } finally {
            junit testResults: 'reports/*.xml', allowEmptyResults: true
          }
        }

        if (env.BRANCH_NAME == 'main') {
          stage('Deploy') {
            sh "./deploy.sh ${target}"
          }
        }
      } catch (err) {
        currentBuild.result = 'FAILURE'
        throw err
      } finally {
        deleteDir()
      }
    }
  }
}

Scripted syntax makes ordinary programming constructs natural: loops, maps, methods, try/catch/finally, and dynamically created branches. It also makes it easier to build a Jenkinsfile that only its author understands. There is less structural validation, and code outside node may execute on the controller rather than an agent.

---

Where Each Construct Lives

NeedDeclarativeScripted
Allocate an agentagent { label 'linux' }node('linux')
Run commandsInside stepsDirectly inside stage or node
Conditional stagewhenGroovy if
Cleanuppost { always { ... } }try/finally
Controlled failurecatchError in stepstry/catch or catchError
Static fan-outDeclarative parallel or matrixparallel map
Dynamic fan-outscript { parallel map }Native parallel map

Declarative is not “Pipeline without Groovy.” Expressions, shared-library calls, and a script step still use Groovy. The useful distinction is how much freedom the top-level Jenkinsfile permits.

---

A Good Hybrid Pattern

Keep orchestration Declarative and isolate the small part that genuinely needs programming:

stage('Component Tests') {
  steps {
    script {
      def branches = [:]
      ['api', 'worker', 'web'].each { component ->
        def name = component
        branches[name] = {
          sh "./ci/test-component.sh ${name}"
        }
      }
      parallel branches
    }
  }
}

The list-to-map conversion belongs in script; checkout, timeouts, credentials, promotion, and cleanup remain visible as Declarative structure. Put heavy parsing or business logic in a versioned script executed on the agent, not in Groovy on the Jenkins controller.

---

Which Should You Learn First?

Learn Declarative first if you build normal CI/CD flows. Be fluent in agent, environment, options, parameters, when, parallel, matrix, and post. Then learn enough Scripted Pipeline to read node, construct a parallel map, and implement reliable try/catch/finally recovery.

Choose fully Scripted Pipeline only when the pipeline topology is truly data-driven, when maintaining an older Scripted estate, or when a required control flow has no clear Declarative representation. Complexity alone is not a reason: a complicated release process usually benefits from stronger structure, smaller shell tools, and a shared library.

Continue with the Jenkins theory course, then compare the solutions in Generate Parallel Work Programmatically and Guarantee Scripted Cleanup.