Jenkins has long been one of the most popular tools for continuous integration and continuous delivery (CI/CD). At the heart of Jenkins Pipelines is Groovy, a powerful scripting language for the Java Virtual Machine (JVM). While Jenkins provides a declarative syntax for writing pipelines, using Groovy unlocks advanced flexibility—allowing you to define custom logic, create reusable functions, and build dynamic stages that adapt to different environments.
This tutorial will guide you through using Groovy for Jenkins Pipelines, from basic syntax to real-world examples. You’ll learn when to use declarative vs scripted pipelines, how to embed Groovy logic directly in your Jenkinsfile, and how to structure shared libraries for clean, maintainable pipelines. By the end, you’ll have a solid understanding of how to harness Groovy to supercharge your CI/CD workflows.
Prerequisites
Before diving into the code, make sure you have the following:
-
Jenkins installed (preferably the latest LTS version). You’ll also need the Pipeline plugin installed, which is included in most Jenkins distributions.
-
Basic Groovy knowledge: familiarity with variables, loops, and methods will help, though we’ll cover essentials.
-
A Git repository with sample code that we can use for the pipeline (you can use any project, even a simple “Hello World” app).
-
Access to a build environment (e.g., Docker, Maven, or Gradle), depending on the type of project you’ll integrate with Jenkins.
-
Admin access to Jenkins to create jobs, configure agents, and install plugins if needed.
With these prerequisites in place, you’re ready to start building pipelines powered by Groovy.
Setting Up a Jenkins Pipeline
Jenkins Pipelines are defined in a special file called Jenkinsfile
, which lives inside your project’s source code repository. This makes pipelines version-controlled, reproducible, and easy to collaborate on with your team.
Here’s how to set up your first pipeline:
1. Create a New Pipeline Job
-
Log in to your Jenkins dashboard.
-
Click “New Item” in the left menu.
-
Enter a name for your job (e.g.,
GroovyPipelineDemo
). -
Select “Pipeline” as the project type, then click OK.
2. Configure the Pipeline
Inside the new job:
-
Go to the Pipeline section.
-
Under Definition, choose:
-
Pipeline script if you want to paste your pipeline code directly into Jenkins.
-
Pipeline script from SCM if you want Jenkins to fetch the
Jenkinsfile
from your Git repository (recommended).
-
For this tutorial, we’ll use the Pipeline script option to keep things simple.
3. Write Your First Jenkinsfile
Let’s start with a basic declarative pipeline written in Groovy syntax:
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building the project...'
}
}
stage('Test') {
steps {
echo 'Running tests...'
}
}
stage('Deploy') {
steps {
echo 'Deploying the application...'
}
}
}
}
📌 How it works:
-
pipeline {}
is the root block that defines a declarative pipeline. -
agent any
tells Jenkins to run the pipeline on any available agent node. -
stages {}
defines the different phases (Build, Test, Deploy). -
steps {}
contains the actual commands—in this case, simpleecho
statements.
4. Run the Pipeline
-
Save the job and click Build Now.
-
Check the Console Output to see your pipeline running step by step.
At this point, you’ve successfully created your first Jenkins Pipeline using Groovy under the declarative syntax. Next, we’ll go deeper into Groovy basics to understand how scripting enhances pipelines beyond simple echo steps.
Groovy Basics Refresher for Pipelines
Before diving into advanced Jenkins pipeline scripting, it’s helpful to refresh some core Groovy concepts. Jenkins pipelines (both declarative and scripted) rely heavily on Groovy, so knowing its syntax will make your Jenkinsfiles cleaner and more powerful.
1. Variables
Groovy uses def
to define variables, but you can also specify types explicitly:
def projectName = "MyApp"
int buildNumber = 42
echo "Project: ${projectName}, Build: ${buildNumber}"
📌 You can embed variables inside strings using ${}
(string interpolation).
2. Strings
Groovy supports both single and double quotes:
def single = 'Single quoted string'
def double = "Double quoted string with variable: ${projectName}"
Double-quoted strings allow interpolation, while single-quoted strings are literal.
3. Lists
Lists (arrays) are simple collections of values:
def stages = ['Build', 'Test', 'Deploy']
stages.each { stageName ->
echo "Stage: ${stageName}"
}
4. Maps
Maps are key-value pairs, useful for configs:
def config = [
appName: "MyApp",
version: "1.0.0",
environment: "staging"
]
echo "Deploying ${config.appName} version ${config.version} to ${config.environment}"
5. Closures (Anonymous Functions)
Closures are blocks of code that can be stored in variables and reused:
def sayHello = { name ->
echo "Hello, ${name}!"
}
sayHello("Jenkins")
Closures are heavily used in Jenkins pipelines, especially with iteration (each
) and pipeline DSL blocks.
6. Methods
You can define reusable methods in your pipeline script:
def greet(name) {
echo "Welcome, ${name}!"
}
greet("Developer")
Putting It Together
Here’s a small Jenkinsfile snippet that uses these Groovy basics:
pipeline {
agent any
stages {
stage('Demo') {
steps {
script {
def services = ['auth', 'payment', 'orders']
services.each { svc ->
echo "Building service: ${svc}"
}
def envConfig = [env: "staging", url: "http://staging.example.com"]
echo "Deploying to ${envConfig.env} at ${envConfig.url}"
}
}
}
}
}
✅ In this example, we used lists, maps, and closures inside a script {}
block to bring Groovy power into a declarative pipeline.
Next, we’ll explore how to use these Groovy features in Declarative Pipelines with practical examples (Build → Test → Deploy flow).
Declarative Pipeline with Groovy
Declarative pipelines are the most commonly used style in Jenkins because they’re structured, easy to read, and enforce a consistent format. Under the hood, they still rely on Groovy, but the syntax is simplified compared to fully scripted pipelines.
In this section, we’ll enhance a declarative pipeline by embedding Groovy logic inside script {}
blocks. This allows us to mix structured stages with flexible Groovy scripting when needed.
1. A Simple Declarative Pipeline
Here’s the starting point:
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building the project...'
}
}
stage('Test') {
steps {
echo 'Running tests...'
}
}
stage('Deploy') {
steps {
echo 'Deploying the application...'
}
}
}
}
This works, but it’s static. Let’s introduce some Groovy power.
2. Using Groovy Variables and Conditions
We can define environment-specific behavior using Groovy:
pipeline {
agent any
environment {
DEPLOY_ENV = 'staging'
}
stages {
stage('Build') {
steps {
echo "Building application in ${DEPLOY_ENV} environment"
}
}
stage('Test') {
steps {
script {
def testSuites = ['unit', 'integration']
testSuites.each { suite ->
echo "Running ${suite} tests..."
}
}
}
}
stage('Deploy') {
when {
expression { return env.DEPLOY_ENV == 'staging' }
}
steps {
echo "Deploying to ${env.DEPLOY_ENV}"
}
}
}
}
📌 Highlights:
-
environment {}
defines global variables available in the pipeline. -
script {}
allows embedding full Groovy logic. -
when { expression { ... } }
uses Groovy conditions to control stage execution.
3. Dynamic Stages with Groovy
Groovy lets us generate stages dynamically at runtime:
pipeline {
agent any
stages {
stage('Dynamic Build') {
steps {
script {
def services = ['auth', 'payment', 'orders']
services.each { svc ->
echo "Building service: ${svc}"
}
}
}
}
}
}
Instead of manually writing a stage for each service, Groovy loops help us scale pipelines effortlessly.
4. Parameters with Groovy
You can make pipelines interactive with parameters:
pipeline {
agent any
parameters {
choice(name: 'ENV', choices: ['dev', 'staging', 'prod'], description: 'Select environment')
}
stages {
stage('Deploy') {
steps {
echo "Deploying to ${params.ENV}"
}
}
}
}
Now Jenkins will prompt you to pick an environment before running the pipeline.
✅ Takeaway: Declarative pipelines give you structure, while Groovy brings flexibility when you need it. By combining both, you can build clean, maintainable pipelines without losing control over dynamic behavior.
Next, we’ll go deeper into the Scripted Pipeline with Groovy, where Groovy takes full control of the pipeline logic.
Scripted Pipeline with Groovy
Unlike declarative pipelines, which enforce a structured, opinionated syntax, scripted pipelines give you complete control by writing your pipeline entirely in Groovy. This approach is more flexible but also more complex, since you need to manage flow, stages, and steps manually.
Scripted pipelines are useful when:
-
You need dynamic pipeline behavior not easily expressed in declarative syntax.
-
You’re building pipelines with loops, advanced conditions, or error handling.
-
You want to leverage full Groovy scripting capabilities.
1. Basic Scripted Pipeline
Here’s the simplest example of a scripted pipeline:
node {
stage('Build') {
echo 'Building the project...'
}
stage('Test') {
echo 'Running tests...'
}
stage('Deploy') {
echo 'Deploying the application...'
}
}
📌 How it works:
-
node {}
defines a Jenkins agent (likeagent any
in declarative). -
stage('...') {}
explicitly defines each stage. -
echo
or other steps are called directly inside stages.
2. Using Variables and Loops
Scripted pipelines let you define and reuse variables more flexibly:
node {
def services = ['auth', 'payment', 'orders']
stage('Build Services') {
services.each { svc ->
echo "Building service: ${svc}"
}
}
stage('Test') {
for (int i = 1; i <= 3; i++) {
echo "Running test batch #${i}"
}
}
}
3. Conditions and Branching
You can use standard Groovy if/else
logic:
node {
def env = "staging"
stage('Deploy') {
if (env == "production") {
echo "Deploying to PRODUCTION!"
} else {
echo "Deploying to ${env} environment"
}
}
}
4. Error Handling with Try/Catch
One of the advantages of scripted pipelines is fine-grained error control:
node {
stage('Test') {
try {
echo "Running risky tests..."
// Simulate error
sh 'exit 1'
} catch (err) {
echo "Caught error: ${err}"
currentBuild.result = 'FAILURE'
} finally {
echo "Cleaning up after tests"
}
}
}
📌 try/catch/finally
lets you handle failures gracefully and decide whether to fail or continue the pipeline.
5. Parallel Execution
You can easily run tasks in parallel using Groovy maps:
node {
stage('Parallel Testing') {
parallel (
"Unit Tests": {
echo "Running unit tests..."
},
"Integration Tests": {
echo "Running integration tests..."
}
)
}
}
This runs both test stages simultaneously, saving build time.
✅ Takeaway: Scripted pipelines are more verbose than declarative ones, but they offer maximum flexibility. They’re best used when you need advanced control structures, parallelization, or custom error handling that doesn’t fit well into declarative pipelines.
Next up, we’ll explore Shared Libraries, which let you extract Groovy logic into reusable functions and keep your Jenkinsfiles clean.
Shared Libraries in Jenkins Pipelines
As your pipelines grow, your Jenkinsfiles can quickly become large and repetitive. Shared Libraries let you extract Groovy code into reusable modules, making your pipelines cleaner, modular, and easier to maintain.
Shared Libraries are stored in a separate Git repository (or a directory in Jenkins), and can be loaded into any pipeline with a single line.
1. Why Use Shared Libraries?
-
Reusability: Common logic (build steps, deployment routines) can be shared across projects.
-
Maintainability: Update logic in one place, all pipelines benefit.
-
Readability: Keeps Jenkinsfiles short and focused on workflow.
2. Directory Structure of a Shared Library
A Shared Library typically has this structure:
(root)
└── vars/
└── utils.groovy
└── src/
└── org/example/Helper.groovy
└── resources/
└── config.json
-
vars/
→ Contains global pipeline functions (simple.groovy
files, automatically exposed). -
src/
→ Contains Groovy classes organized by package. -
resources/
→ Stores non-code files like JSON configs or templates.
3. Example: utils.groovy
in vars/
Create a utils.groovy
file inside vars/
:
def sayHello(String name) {
echo "Hello, ${name}!"
}
def deployApp(String env) {
echo "Deploying application to ${env}..."
}
Each function becomes available to any pipeline once the library is loaded.
4. Using the Shared Library in a Pipeline
First, configure Jenkins:
-
Go to Manage Jenkins → Configure System → Global Pipeline Libraries.
-
Add a new library:
-
Name:
my-shared-lib
-
Default version:
main
(or your Git branch) -
Source: your Git repo URL.
-
Then, load it in your Jenkinsfile:
@Library('my-shared-lib') _
pipeline {
agent any
stages {
stage('Greeting') {
steps {
script {
utils.sayHello("Djamware")
}
}
}
stage('Deploy') {
steps {
script {
utils.deployApp("staging")
}
}
}
}
}
📌 The @Library
annotation loads the shared library. Now you can call utils.sayHello()
or any other function defined in vars/
.
5. Using Classes from src/
For more complex logic, you can create classes inside src/
:
// src/org/example/Helper.groovy
package org.example
class Helper {
static void notifyTeam(String message) {
println "Sending notification: ${message}"
}
}
Then use it in your pipeline:
@Library('my-shared-lib') _
import org.example.Helper
node {
stage('Notify') {
steps {
script {
Helper.notifyTeam("Build completed successfully!")
}
}
}
}
✅ Takeaway: Shared Libraries let you centralize and modularize your Groovy code. This keeps Jenkinsfiles clean and allows teams to scale pipelines across multiple projects without duplication.
Next, we’ll move into Practical Examples — running shell commands, handling errors, and creating dynamic pipelines with Groovy.
Practical Examples with Groovy in Jenkins Pipelines
So far, we’ve looked at structured pipelines and reusable Groovy functions. Now let’s apply them to real-world use cases you’ll likely encounter in CI/CD.
1. Running Shell Commands
You can run shell or batch commands directly inside pipelines.
pipeline {
agent any
stages {
stage('Build') {
steps {
script {
sh 'echo "Compiling source code..."'
sh './gradlew build'
}
}
}
}
}
📌 Use sh
for Linux/macOS or bat
for Windows.
2. Dynamic Stages with Groovy
Instead of writing dozens of repetitive stages, you can generate them dynamically:
pipeline {
agent any
stages {
stage('Build Services') {
steps {
script {
def services = ['auth', 'payment', 'orders']
services.each { svc ->
echo "Building ${svc}..."
sh "./build-${svc}.sh"
}
}
}
}
}
}
This loops through services and runs their respective build scripts.
3. Parallel Execution
Speed up pipelines by running tasks in parallel:
pipeline {
agent any
stages {
stage('Tests') {
parallel {
stage('Unit Tests') {
steps {
sh './gradlew test'
}
}
stage('Integration Tests') {
steps {
sh './gradlew integrationTest'
}
}
}
}
}
}
📌 Parallel execution is especially useful for long-running test suites.
4. Handling Errors with Try/Catch
Groovy lets you gracefully handle failures instead of halting the whole pipeline.
pipeline {
agent any
stages {
stage('Risky Operation') {
steps {
script {
try {
sh 'exit 1' // Simulated failure
} catch (Exception e) {
echo "Caught an error: ${e}"
currentBuild.result = 'UNSTABLE'
} finally {
echo "Always run cleanup steps here"
}
}
}
}
}
}
This ensures cleanup always runs, even if a stage fails.
5. Using External Config Files
Groovy can read JSON or YAML files to configure pipelines dynamically.
@Library('my-shared-lib') _
import groovy.json.JsonSlurper
pipeline {
agent any
stages {
stage('Deploy') {
steps {
script {
def configFile = readFile 'config.json'
def config = new JsonSlurper().parseText(configFile)
echo "Deploying ${config.appName} to ${config.environment}"
}
}
}
}
}
📌 This is useful when you want environment-based deployments without hardcoding values.
✅ Takeaway: These practical examples show how Groovy makes Jenkins pipelines dynamic, fault-tolerant, and scalable. You can generate stages on the fly, run commands in parallel, handle errors gracefully, and even load configurations from external files.
Next up, we’ll wrap this tutorial with Best Practices to keep your pipelines clean, maintainable, and production-ready.
Best Practices for Using Groovy in Jenkins Pipelines
As powerful as Groovy is in Jenkins, it’s easy for pipelines to become messy if you don’t follow some conventions. These best practices will help you keep pipelines clean, maintainable, and scalable.
1. Prefer Declarative Pipelines for Readability
-
Use declarative pipelines as the default since they’re easier to read and enforce structure.
-
Drop into
script {}
blocks only when Groovy logic is absolutely necessary. -
Use scripted pipelines only when you need maximum flexibility.
2. Keep Jenkinsfiles Short and Focused
-
Limit Jenkinsfiles to workflow orchestration (stages, steps, environment).
-
Extract reusable Groovy code into Shared Libraries (
vars/
orsrc/
). -
This makes pipelines easier to review and version control.
3. Use Parameters Instead of Hardcoding
-
Always use parameters (
choice
,string
,booleanParam
) for things like environments, versions, or feature toggles. -
Avoid hardcoding sensitive or environment-specific values.
Example:
parameters {
string(name: 'APP_VERSION', defaultValue: '1.0.0', description: 'Application version')
}
4. Handle Errors Gracefully
-
Wrap risky operations in
try/catch/finally
. -
Set
currentBuild.result
(SUCCESS
,FAILURE
,UNSTABLE
) explicitly for clarity. -
Use
post {}
in declarative pipelines to manage success/failure notifications.
Example:
post {
success {
echo 'Build succeeded!'
}
failure {
echo 'Build failed!'
}
}
5. Organize Stages Logically
-
Use clear stage names that describe intent (e.g., Build Backend, Run Unit Tests, Deploy to Staging).
-
Group related tasks inside a stage instead of scattering them.
-
Use parallel wisely for independent tasks.
6. Externalize Configuration
-
Store configuration in JSON, YAML, or environment variables instead of embedding it in Jenkinsfiles.
-
This makes pipelines adaptable to multiple environments.
7. Secure Sensitive Data
-
Use Jenkins Credentials Plugin for API keys, SSH keys, and passwords.
-
Never commit secrets to Jenkinsfiles or Shared Libraries.
-
Access credentials via
withCredentials
block.
Example:
withCredentials([usernamePassword(credentialsId: 'docker-cred', usernameVariable: 'USER', passwordVariable: 'PASS')]) {
sh "docker login -u $USER -p $PASS"
}
✅ Takeaway:
-
Use declarative syntax for simplicity.
-
Keep pipelines short, move logic to shared libraries.
-
Always parameterize, secure, and externalize configurations.
-
Handle errors gracefully and write pipelines that scale across multiple teams and environments.
Next, we’ll finish this tutorial with a Conclusion + Next Steps section, summarizing everything and pointing readers toward further learning.
Conclusion + Next Steps
In this tutorial, we explored how Groovy powers Jenkins Pipelines, from simple declarative workflows to fully scripted pipelines. We started by setting up a basic pipeline, reviewed Groovy essentials, and then applied them in both declarative and scripted styles. We also saw how to use Shared Libraries to keep Jenkinsfiles clean and reusable, and we walked through practical examples like running shell commands, handling errors, dynamic stages, and parallel execution. Finally, we covered best practices to keep pipelines maintainable, secure, and scalable.
By now, you should be comfortable with:
-
The difference between Declarative and Scripted pipelines.
-
Embedding Groovy logic inside Jenkinsfiles.
-
Writing reusable code with Shared Libraries.
-
Applying Groovy to real-world CI/CD scenarios.
Next Steps
To deepen your knowledge and improve your pipelines further, you can:
-
Explore the official Jenkins Pipeline Documentation.
-
Learn more about Groovy language basics.
-
Implement advanced features like pipeline libraries with unit testing.
-
Integrate with external tools (Docker, Kubernetes, Slack, GitHub Actions).
-
Apply these concepts to your organization’s CI/CD pipelines for real-world impact.
With Groovy, Jenkins pipelines transform from simple automation scripts into powerful, dynamic workflows that scale with your team’s needs. 🚀
You can find the full script or source code on our GitHub.
That's just the basics. If you need more deep learning about Groovy and Grails, you can take the following cheap course:
- Mastering Grails. A Comprehensive Grails Course.
- Intro to web programming with Groovy on Grails
- Groovy Programming Fundamentals for Java Developers
- The Complete Apache Groovy Developer Course
- Groovy Fundamentals For Testers - Step By Step
- Webservice Automation using SoapUI Groovy and Maven
Thanks!