Declarative vs Scripted
Jenkins has two pipeline syntaxes. This page explains both and helps you choose.
Quick Comparison
| Feature | Declarative | Scripted |
|---|---|---|
| Syntax | Structured, predefined blocks | Free-form Groovy |
| Learning curve | Easy | Steep |
| Validation | Built-in syntax check | No validation |
| Restart from stage | Yes | No |
| Error handling | post { } block |
try/catch/finally |
| Flexibility | Limited (by design) | Unlimited |
| Recommendation | Default choice | Only when needed |
Declarative Pipeline
Declarative is the recommended syntax. It has clear structure and built-in validation.
pipeline {
agent any
options {
timeout(time: 30, unit: 'MINUTES')
}
stages {
stage('Build') {
steps {
sh 'make build'
}
}
stage('Test') {
steps {
sh 'make test'
}
}
}
post {
always {
echo 'Pipeline finished'
}
failure {
echo 'Pipeline failed!'
}
}
}
Advantages
- Clean, readable structure
- Syntax validation before execution
- Can restart from a failed stage
- Built-in
postblock for cleanup whenconditions for conditional stages- Easy to learn for new users
Scripted Pipeline
Scripted pipeline uses full Groovy language. Use it only when declarative cannot do what you need.
node {
try {
stage('Build') {
sh 'make build'
}
stage('Test') {
sh 'make test'
}
} catch (Exception e) {
echo "Pipeline failed: ${e.message}"
throw e
} finally {
echo 'Cleanup'
}
}
When to Use Scripted
- Complex dynamic logic (generating stages at runtime)
- Advanced Groovy scripting needs
- Legacy pipelines that already use it
Avoid scripted for new pipelines
Almost everything you need can be done with declarative syntax. Start with declarative and switch to scripted only if you hit a real limitation.
Using Groovy Inside Declarative
You can use script { } blocks inside declarative for small pieces of Groovy logic:
pipeline {
agent any
stages {
stage('Dynamic') {
steps {
script {
def version = sh(
script: 'cat VERSION',
returnStdout: true
).trim()
echo "Version: ${version}"
}
}
}
}
}
Keep script blocks small
If you have many script { } blocks, consider moving logic to a Shared Library instead.
Migration: Scripted to Declarative
| Scripted | Declarative |
|---|---|
node { } |
pipeline { agent any } |
stage('X') { sh '...' } |
stage('X') { steps { sh '...' } } |
try/catch/finally |
post { failure { } always { } } |
if (condition) { } |
when { expression { } } |
| Global variables | environment { } block |
Best Practices
- Use Declarative for all new pipelines
- Use
script { }blocks sparingly inside declarative - Move complex Groovy logic to Shared Libraries
- Keep pipeline code simple — use
shsteps for heavy lifting - Use
post { }block for cleanup, notifications, and reporting