How can I deep copy a map of maps in Groovy? The map keys are Strings or Ints. The values are Strings, Primitive Objects or other maps, in a recursive way.
5 Answers
An easy way is this:
// standard deep copy implementation
def deepcopy(orig) {
bos = new ByteArrayOutputStream()
oos = new ObjectOutputStream(bos)
oos.writeObject(orig); oos.flush()
bin = new ByteArrayInputStream(bos.toByteArray())
ois = new ObjectInputStream(bin)
return ois.readObject()
}

- 11,265
- 16
- 66
- 92
-
Superb. I'm using this to deep copy a groovy ConfigObject as clone() only does a shallow copy. The shallow copy isn't sufficient on a ConfigObject for my needs. Thanks. – nonbeing Jan 22 '13 at 09:56
-
1@noumenon how do you use this for deepcoying ConfigObject, as ConfigObject is not serializable – Sudhir N Dec 16 '14 at 12:32
-
2This doesn't work for Json either. `java.io.NotSerializableException: groovy.json.internal.LazyMap` – C. Ross Apr 18 '16 at 14:41
-
Note [@AutoClone annotation with AutoCloneStyle.SERIALIZATION](http://www.groovy-lang.org/single-page-documentation.html) does this for you – solstice333 Mar 08 '17 at 04:50
-
this doesnt seem to work for closures, any ideas? java.lang.ClassNotFoundException: org.jenkinsci.plugins.workflow.cps.CpsClosure2 – fersarr Dec 29 '17 at 15:03
-
that will not work with a locally (or anywhere?) defined "enum" member value - at least that far as i tried it out. - so please take care. – Alexander Stohr Jan 20 '22 at 12:59
-
that might not work if one of the values is maybe a reference to a system or library function. - please find your own application specific solution. – Alexander Stohr Jan 20 '22 at 13:21
-
1The OP specifies the values are simple objects, or maps of such. So this should work. – Ayman Jan 24 '22 at 04:59
-
I cant understand why the answer is high rated, java.io.ByteArrayOutputStream is not serializable. Means you can not generate any stages within jenkins with it. maybe it fits for use-cases beyond jenkins. But would u use groovy beyond jenkins? – Nikolai Ehrhardt Sep 22 '22 at 10:02
To go about deep copying each member in a class, the newInstance() exists for Class objects. For example,
foo = ["foo": 1, "bar": 2]
bar = foo.getClass().newInstance(foo)
foo["foo"] = 3
assert(bar["foo"] == 1)
assert(foo["foo"] == 3)
See http://groovy-lang.org/gdk.html and navigate to java.lang, Class, and finally the newInstance method overloads.
UPDATE:
The example I have above is ultimately an example of a shallow copy, but what I really meant was that in general, you almost always have to define your own reliable deep copy logic, with perhaps using the newInstance() method, if the clone() method is not enough. Here's several ways how to go about that:
import groovy.transform.Canonical
import groovy.transform.AutoClone
import static groovy.transform.AutoCloneStyle.*
// in @AutoClone, generally the semantics are
// 1. clone() is called if property implements Cloneable else,
// 2. initialize property with assignment, IOW copy by reference
//
// @AutoClone default is to call super.clone() then clone() on each property.
//
// @AutoClone(style=COPY_CONSTRUCTOR) which will call the copy ctor in a
// clone() method. Use if you have final members.
//
// @AutoClone(style=SIMPLE) will call no arg ctor then set the properties
//
// @AutoClone(style=SERIALIZATION) class must implement Serializable or
// Externalizable. Fields cannot be final. Immutable classes are cloned.
// Generally slower.
//
// if you need reliable deep copying, define your own clone() method
def assert_diffs(a, b) {
assert a == b // equal objects
assert ! a.is(b) // not the same reference/identity
assert ! a.s.is(b.s) // String deep copy
assert ! a.i.is(b.i) // Integer deep copy
assert ! a.l.is(b.l) // non-identical list member
assert ! a.l[0].is(b.l[0]) // list element deep copy
assert ! a.m.is(b.m) // non-identical map member
assert ! a.m['mu'].is(b.m['mu']) // map element deep copy
}
// deep copy using serialization with @AutoClone
@Canonical
@AutoClone(style=SERIALIZATION)
class Bar implements Serializable {
String s
Integer i
def l = []
def m = [:]
// if you need special serialization/deserialization logic override
// writeObject() and/or readObject() in class implementing Serializable:
//
// private void writeObject(ObjectOutputStream oos) throws IOException {
// oos.writeObject(s)
// oos.writeObject(i)
// oos.writeObject(l)
// oos.writeObject(m)
// }
//
// private void readObject(ObjectInputStream ois)
// throws IOException, ClassNotFoundException {
// s = ois.readObject()
// i = ois.readObject()
// l = ois.readObject()
// m = ois.readObject()
// }
}
// deep copy by using default @AutoClone semantics and overriding
// clone() method
@Canonical
@AutoClone
class Baz {
String s
Integer i
def l = []
def m = [:]
def clone() {
def cp = super.clone()
cp.s = s.class.newInstance(s)
cp.i = i.class.newInstance(i)
cp.l = cp.l.collect { it.getClass().newInstance(it) }
cp.m = cp.m.collectEntries { k, v ->
[k.getClass().newInstance(k), v.getClass().newInstance(v)]
}
cp
}
}
// assert differences
def a = new Bar("foo", 10, ['bar', 'baz'], [mu: 1, qux: 2])
def b = a.clone()
assert_diffs(a, b)
a = new Baz("foo", 10, ['bar', 'baz'], [mu: 1, qux: 2])
b = a.clone()
assert_diffs(a, b)
I used @Canonical
for the equals() method and tuple ctor. See groovy doc Chapter 3.4.2, Code Generation Transformations.
Another way to go about deep copying is using mixins. Let's say you wanted an existing class to have deep copy functionality:
class LinkedHashMapDeepCopy {
def deep_copy() {
collectEntries { k, v ->
[k.getClass().newInstance(k), v.getClass().newInstance(v)]
}
}
}
class ArrayListDeepCopy {
def deep_copy() {
collect { it.getClass().newInstance(it) }
}
}
LinkedHashMap.mixin(LinkedHashMapDeepCopy)
ArrayList.mixin(ArrayListDeepCopy)
def foo = [foo: 1, bar: 2]
def bar = foo.deep_copy()
assert foo == bar
assert ! foo.is(bar)
assert ! foo['foo'].is(bar['foo'])
foo = ['foo', 'bar']
bar = foo.deep_copy()
assert foo == bar
assert ! foo.is(bar)
assert ! foo[0].is(bar[0])
Or categories (again see the groovy doc) if you wanted deep copying semantics based on some sort of runtime context:
import groovy.lang.Category
@Category(ArrayList)
class ArrayListDeepCopy {
def clone() {
collect { it.getClass().newInstance(it) }
}
}
use(ArrayListDeepCopy) {
def foo = ['foo', 'bar']
def bar = foo.clone()
assert foo == bar
assert ! foo.is(bar)
assert ! foo[0].is(bar[0]) // deep copying semantics
}
def foo = ['foo', 'bar']
def bar = foo.clone()
assert foo == bar
assert ! foo.is(bar)
assert foo[0].is(bar[0]) // back to shallow clone

- 3,399
- 1
- 31
- 28
-
BEWARE - a sequence like this is only a first-level copy - but NOT a deep copy: Map copy1stLevel = foo.getClass().newInstance(foo) – Alexander Stohr Jan 19 '22 at 16:27
For Json (LazyMap) this wokred for me
copyOfMap = new HashMap<>()
originalMap.each { k, v -> copyOfMap.put(k, v) }
copyOfMap = new JsonSlurper().parseText(JsonOutput.toJson(copyOfMap))
EDIT: Simplification by: Ed Randall
copyOfMap = new JsonSlurper().parseText(JsonOutput.toJson(originalMap))

- 643
- 1
- 6
- 12
-
2This is effectively a simpler serialization of the accepted answer. But surely lines 1 & 2 are superfluous, you just need the one line: ```copyOfMap = new JsonSlurper().parseText(JsonOutput.toJson(originalMap))``` – Ed Randall Nov 26 '18 at 08:11
-
1The only issue I've encountered is that ```JsonSlurper``` creates a clone backed by ```LazyMap``` which can later fall foul of ```java.io.NotSerializableException: groovy.json.internal.LazyMap``` – Ed Randall Nov 26 '18 at 08:32
-
(Edit queue is full - whatever that means for the quality of this answer.) – Alexander Stohr Jan 19 '22 at 16:29
I've just hit this issue as well, and I just found:
deepCopy = evaluate(original.inspect())
Although I've been coding in Groovy for less than 12 hours, I wonder if there might be some trust issues with using evaluate
. Also, the above doesn't handle backslashes. This:
deepCopy = evaluate(original.inspect().replace('\\','\\\\'))
does.

- 342
- 2
- 8
-
2Worked for me when editing docker-compose.yml with groovy and jenkins. Thanks! (added this comment for easier google search) – andrzej.szmukala Oct 04 '17 at 13:33
-
1Doesn't work in case if you have something like this: a = 'test'; ['foo': 1, 'bar': "${a}"] – NULL Apr 12 '19 at 15:32
-
I am afraid you have to do it the clone
way. You could give Apache Commons Lang SerializationUtils a try

- 4,213
- 3
- 29
- 38
-
Clone seems to work well for maps. But as soon as you bring more complicated objects onto the scene, it doesn't work as well, because it's a shallow copy. Ayman's deep copy is fast and works for the objects that I tried (including ConfigObject). – nonbeing Jan 22 '13 at 09:58