Jenkins Error: 'com.cloudbees.groovy.cps.impl.CpsCallableInvocation ... Not serializable' — Cause, Fix, and Troubleshooting Guide
Fix Jenkins Pipeline CPS 'CpsCallableInvocation ... Not serializable': learn why state is serialized across steps, and use @NonCPS and restructuring to fix it.
- #jenkins
- #ci-cd
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
Jenkins Pipeline runs your Groovy through a Continuation-Passing-Style (CPS) interpreter so it can pause at any step, serialize the entire program state to disk, and resume later — even after a controller restart. When the interpreter reaches a step boundary and finds a local variable it cannot serialize, the build fails with a message like:
an exception which occurred:
in field groovy.lang.Closure.delegate
in object com.cloudbees.groovy.cps.impl.CpsCallableInvocation@1a2b3c
Caused: java.io.NotSerializableException: java.util.regex.Matcher
... com.cloudbees.groovy.cps.impl.CpsCallableInvocation ...
Not serializable
The CpsCallableInvocation in the trace is CPS trying to capture the continuation across a step. The actual offender is named further down (here, a java.util.regex.Matcher) — a non-serializable object being held in a local variable across a sh, sleep, input, or any other Pipeline step.
Symptoms
- The build fails at a Pipeline step (
sh,sleep,input,stage,parallel) rather than at plain Groovy. - The trace mentions
com.cloudbees.groovy.cps.impl.CpsCallableInvocationand aNotSerializableExceptionnaming a concrete type (Matcher,Pattern,JsonSlurperClassicresult, an SSHClient, a JDBCConnection, etc.). - The offending type is often the result of a regex match, a JSON/XML parse, or a live handle (socket, file, DB connection).
- Moving code that “worked in a script” into a Pipeline
stepsblock suddenly triggers it. - The failure is reproducible and deterministic, unlike environmental flakes.
Common Root Causes
- A non-serializable local held across a step — you assign something like
def m = (log =~ /error/)(aMatcher) and then callsh/sleep; CPS must serializemat that boundary and cannot. - Parsing with a non-CPS-friendly type —
new JsonSlurper().parseText(...)returns aLazyMap/non-serializable object; usingreadJSON/JsonSlurperClassicand keeping the raw parser objects around causes it. - Live resource handles kept across steps — database connections, HTTP clients, or file streams opened in one step and used after another step cannot be persisted.
- Iterators/streams across a step —
Iterator,Stream, and some collection views are not serializable; iterating them with a step inside the loop body triggers it. - Closures capturing non-serializable delegates — a closure that closes over a non-serializable object gets dragged into the continuation.
- Method that should be
@NonCPSreturning a CPS-transformed object — mixing CPS and non-CPS incorrectly leaves an un-persistable invocation on the stack.
How to diagnose
Read past the CpsCallableInvocation frames to the Caused: java.io.NotSerializableException: <Type> line — that concrete type is the object you must stop holding across a step.
Identify the step boundary. The failure occurs at the first Pipeline step after the non-serializable local is created. In the console output, find the last successful line before the exception; the offending assignment is just above it in the Jenkinsfile.
Reproduce and inspect quickly in Manage Jenkins → Script Console (this runs plain Groovy, not CPS, so use it only to confirm which types are non-serializable):
// Which types are (not) serializable?
println( (java.io.Serializable).isAssignableFrom(java.util.regex.Matcher) ) // false
println( (java.io.Serializable).isAssignableFrom(String) ) // true
Turn on Pipeline durability/step detail if you need the exact step: Manage Jenkins → System → “Pipeline Speed/Durability Settings” shows the durability mode. In Blue Ocean or the classic Pipeline Steps view, the failing step is highlighted red — open it to see the frame.
If you cannot tell which local is the problem, bisect: temporarily replace the suspect assignment with a serializable primitive (e.g. capture .group() as a String) and see if the failure moves.
Fixes
1. Don’t hold the non-serializable value across a step
Extract the primitive you actually need before the next step, so no Matcher/parser object survives:
// BAD: Matcher is held across sh()
def m = (buildLog =~ /version=(\d+)/)
sh 'sleep 1'
echo m[0][1] // fails: Matcher not serializable across the step
// GOOD: pull the String out immediately, discard the Matcher
def version = (buildLog =~ /version=(\d+)/) ? (buildLog =~ /version=(\d+)/)[0][1] : ''
sh 'sleep 1'
echo version // version is a String — serializable
2. Isolate regex/parsing logic in an @NonCPS method
@NonCPS methods run outside the CPS transform, so local non-serializable objects live and die inside the method and never cross a step. The method must return a serializable value and must not call Pipeline steps:
@NonCPS
String extractVersion(String log) {
def m = (log =~ /version=(\d+)/) // Matcher lives only inside this method
return m ? m[0][1] : '' // returns a plain String
}
node {
def v = extractVersion(currentBuild.rawBuild.log)
sh "echo building ${v}" // v is serializable — safe across the step
}
Do not put sh, checkout, input, etc. inside an @NonCPS method — step calls there behave unpredictably.
3. Use Pipeline-native parsing steps
Prefer the Pipeline Utility Steps that return plain, serializable maps/lists instead of raw parser objects:
def data = readJSON text: sh(script: 'cat config.json', returnStdout: true)
def cfg = readYaml file: 'settings.yaml'
// data / cfg are serializable Maps/Lists — safe to keep across steps
If you must use JsonSlurper, wrap it in an @NonCPS method and return String/Map of primitives.
4. Open and close live handles inside a single non-step scope
Never keep a DB connection, HTTP client, or stream open across a step. Do the whole interaction in one @NonCPS (or one uninterrupted block) and return only the result:
@NonCPS
List<String> queryUsers(String jdbcUrl) {
def out = []
def conn = java.sql.DriverManager.getConnection(jdbcUrl) // handle stays local
try {
def rs = conn.createStatement().executeQuery('SELECT name FROM users')
while (rs.next()) out << rs.getString('name') // copy into a List<String>
} finally {
conn.close()
}
return out // only serializable data escapes
}
5. Convert iterators/views to serializable collections
Replace Java Iterator/Stream usage that spans a step with a materialized list, and use classic for/indexed loops:
def files = sh(script: 'ls *.jar', returnStdout: true).trim().split('\n') as List
for (int i = 0; i < files.size(); i++) {
sh "sha256sum ${files[i]}" // list of Strings, indexed loop — CPS-safe
}
What to watch out for
- This is different from a plain
NotSerializableExceptionon a persisted global or object graph — here the trigger is specifically a CPS continuation captured at a step boundary. If your trace has noCpsCallableInvocationframe, see theNotSerializableExceptionsibling guide instead. @NonCPSis a scalpel, not a blanket fix — a@NonCPSmethod that calls a Pipeline step or returns a non-serializable object creates new, subtler failures.- Higher durability modes (
MAX_SURVIVABILITY) serialize more aggressively and surface these bugs thatPERFORMANCE_OPTIMIZEDmight mask; test in the mode you run in production. - Groovy’s
=~returns aMatcherand==~returns aboolean— prefer==~for pure match tests to avoid ever creating aMatcher. - Loading a Shared Library and holding one of its non-serializable objects across a step triggers the same error — apply the same
@NonCPS/extract-primitive discipline in library code.
Related
- Jenkins Error: ‘java.io.NotSerializableException’ — the broader Pipeline serialization failure when it is not a CPS continuation.
- Jenkins Error: ‘Scripts not permitted to use method’ — the related sandbox/CPS restriction you hit when refactoring into helper methods.
- Jenkins Error: ‘No such DSL method’ — a common follow-on error when moving code between CPS and
@NonCPSscopes.
More Jenkins prompts & error guides
Every Jenkins AI prompt and troubleshooting guide, in one place.
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.