Overview
If you have come across a NotSerializableException
error when working with Jenkins pipelines, specifically with the groovy.json.internal.LazyMap
class, don’t worry—you’re not alone. This problem commonly arises when trying to serialize Groovy’s lazy maps within a pipeline, typically during the serialization/deserialization process that Jenkins uses to save pipeline state across different nodes.
Understanding the Error
The NotSerializableException
in Jenkins pipelines primarily occurs because Jenkins requires every object that persists across different stages or nodes to be serializable. However, Groovy’s LazyMap
, which is often used to parse JSON responses easily, does not implement the java.io.Serializable
interface, thus causing this exception.
Let’s Fix It
Solution #1 – Use @NonCPS Annotation
By marking methods that manipulate non-serializable objects like LazyMap
with the @NonCPS
annotation, you can prevent them from being serialized. This solution forces Jenkins to treat these methods differently, avoiding the serialization process for their execution.
- Step 1: Identify methods that use or manipulate
LazyMap
. - Step 2: Add the
@NonCPS
annotation to these methods.
Code example:
@NonCPS
def parseJson(String json) {
return new groovy.json.JsonSlurper().parseText(json)
}
Notes: This approach has its limitations, such as not being able to use certain pipeline features within the @NonCPS
methods. However, it’s an easy fix for simple scripts.
Solution #2 – Convert LazyMap to Serializable Map
Another way to fix this issue is by converting the LazyMap
into a standard Java map or any other structure that implements the Serializable
interface before using it in a context that requires serialization.
Code example:
def json = new groovy.json.JsonSlurper().parseText(jsonString)
def serializableMap = new HashMap<>(json)
Notes: This solution may introduce some overhead but guarantees that your object is serializable, thus avoiding the NotSerializableException
.
Solution #3 – Use Scripted Pipeline Instead
To address serialization issues such as NotSerializableException
in Jenkins pipelines, especially when working with non-serializable objects like groovy.json.internal.LazyMap
, switching to a scripted pipeline can provide a workaround. Scripted pipelines offer more granular control over the script’s execution context and can handle complex logic that might not be serializable in a declarative pipeline. Here’s an example of how you can rewrite a declarative pipeline into a scripted pipeline:
Declarative Pipeline (Original)
Assuming you have a declarative pipeline that’s causing a NotSerializableException
due to the handling of a JSON response:
pipeline {
agent any
stages {
stage('Process JSON') {
steps {
script {
def jsonResponse = sh(script: 'curl -s http://example.com/api/data', returnStdout: true).trim()
def jsonData = readJSON text: jsonResponse
echo "Data: ${jsonData.key}"
}
}
}
}
}
Scripted Pipeline (Rewritten to Avoid Serialization Issues)
Here’s how you could rewrite the above logic using a scripted pipeline:
node {
// Define stages
stage('Checkout') {
checkout scm
}
stage('Process JSON') {
// Wrap potentially non-serializable steps in a 'node' block
node {
// Shell step to get JSON response
def jsonResponse = sh(script: 'curl -s http://example.com/api/data', returnStdout: true).trim()
// Use Groovy's JsonSlurper for parsing JSON which doesn't need to be serialized
def jsonSlurper = new groovy.json.JsonSlurper()
def jsonData = jsonSlurper.parseText(jsonResponse)
// Non-serializable objects are handled within this block
echo "Data: ${jsonData.key}"
}
}
}
In this scripted pipeline:
- The main logic is wrapped within a
node
block, which specifies that the enclosed steps should run on an agent. - The
stage
directive is used similarly to the declarative pipeline to define stages of the pipeline. - The Groovy
JsonSlurper
is used for parsing the JSON response instead of thereadJSON
step. This is becauseJsonSlurper
does not introduce serialization issues. - Non-serializable objects like the result of
JsonSlurper.parseText()
are handled within thenode
block, avoiding theNotSerializableException
.
By restructuring your pipeline into a scripted format, you can utilize Groovy’s features more flexibly to manage non-serializable objects and control the execution flow to work around serialization issues.
Conclusion
While encountering a NotSerializableException
within Jenkins pipelines can be frustrating, understanding the root causes and applying appropriate fixes can help you overcome this hurdle. Whether you choose to use the @NonCPS
annotation, convert non-serializable objects, or switch pipeline types, the key is to understand your pipeline’s needs and choose the solution that best fits your situation.