Groovy vs Jenkins Groovy

Ben Akselrod
2 min readAug 21, 2017

--

When developing a pipeline or groovy based script, it’s important to note that Jenkin’s groovy is slightly different that regular Groovy. While working on a script that monitors some Cloud based service for discovering performance issues, I ran into errors in Jenkins, such as:

java.io.NotSerializableException: groovy.json.internal.LazyMap
at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:860)
at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:569)
at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65)
at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.java:56)
at org.jboss.marshalling.MarshallerObjectOutputStream.writeObjectOverride(MarshallerObjectOutputStream.java:50)
at org.jboss.marshalling.river.RiverObjectOutputStream.writeObjectOverride(RiverObjectOutputStream.java:179)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at java.util.LinkedHashMap.internalWriteEntries(Unknown Source)
at java.util.HashMap.writeObject(Unknown Source)

This error is caused by Jenkin’s requirement that all data must be serializeable. The requirement stems from the fact that all Jenkins jobs can be stopped at any point (restarts, user intervention, etc), and every variable’s state must be saved as well. Sometimes we use data structures that aren’t serializeable, most commonly when using JsonSlurper.

There are two ways to solve this problem:

  1. Find a way to use a serializeable data structure. JsonSlurper, uses LazyMap which isn’t serializeable, but JsonSlurperClassic uses HashMap which is serializeable. Using the JsonSlurperClassic would eliminate the exception.
  2. If there is a need to use a non-serializable data structure, for example when JsonSlurperClassic is not available, or a method that only exists in JsonSlurper but not classic, Jenkins provides a solution:
    put the code that uses the offending data structure in its own method (good programming practice anyways), and annotate the method with @NonCPS. This tells Jenkins that the method doesn’t need to be serialized, and its state doesn’t need to be saved. More info here:
    https://github.com/jenkinsci/workflow-cps-plugin/blob/master/README.md#technical-design
    code example:

Code that causes an error in Jenkins:

node(‘main’) {

stage(‘get user data’) {
def userDataJson = //code that gets user data in json format
def jsonSlurper = new JsonSlurper()
def resultJson = jsonSlurper.parseText(userDataJson)

resultJson.each {
print “${it.key}”
}
}
}

Using @NonCPS

@NonCPS
def getUserData(String json) {
def jsonSlurper = new JsonSlurper()
def resultJson = jsonSlurper.parseText(json)
resultJson.each {
print “${it.key}”
}
}
node(‘main’) {

stage(‘get user data’) {
def userDataJson = //code that gets user data in json format
getUserData(usersJson)
}
}

When developing, I would suggest using the second approach. This makes the code much more readable, and forces good development practices. Get into the habit of separating your code into methods, so when copying your script into Jenkins, you only have to annotate all the helper methods with @NonCPS to make your code work in Jenkins

--

--