Java Cloning
A clone in Java means creating a new object that has the same state as an existing object. There are a few common approaches to copying objects in Java: using the built-in clone mechanism, writing a copy constructor/factory, or using serialization (or third-party utilities) to perform a deep copy.
This post summarizes the key concepts, trade-offs, and shows small code examples for quick reference.
Key ideas
- Shallow copy: copies primitive fields and references to objects, so both original and copy point to the same referenced objects.
- Deep copy: copies the whole object graph so the copy does not share mutable sub-objects with the original.
- Cloneable: a marker interface. Object.clone() is protected; to expose it you override clone() and usually call super.clone(). If Cloneable is not implemented, super.clone() throws CloneNotSupportedException.
When to deep-copy?
- If your object contains mutable fields (e.g., lists, custom mutable types) and you need an independent copy, use deep copy.
- For immutable fields (String, Integer, records, immutable collections), a shallow copy is fine because their state cannot be altered.
Common approaches
1) Using Cloneable + Object.clone() (shallow by default)
Pros: fast, low boilerplate for simple objects. Cons: tricky to get right for deep copies; fragile with inheritance; exceptions and visibility can be awkward.
Example — shallow clone (default behavior):
public class Address implements Cloneable {
public String city;
public Address(String city) { this.city = city; }
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone(); // field-wise copy (String is immutable)
}
}
public class Person implements Cloneable {
public String name;
public Address address; // mutable reference
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone(); // shallow: 'address' reference copied
}
}
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
Address a = new Address("Mumbai");
Person p1 = new Person("Nitin", a);
Person p2 = p1.clone();
p2.address.city = "Delhi"; // also changes p1.address.city because address is shared
System.out.println(p1.address.city); // prints: Delhi
}
}
2) Making clone() produce a deep copy
To deep-copy using clone(), you must clone or copy mutable sub-objects too.
@Override
public Person clone() throws CloneNotSupportedException {
Person copy = (Person) super.clone();
// deep-copy mutable field(s)
copy.address = this.address != null ? this.address.clone() : null;
return copy;
}
Make sure every mutable nested type either implements Cloneable and clones itself, or you construct new instances for those fields manually.
3) Copy constructor / factory (recommended for clarity)
Pros: explicit, easier to reason about, works well with final fields. Cons: more boilerplate when there are many fields.
public class Person {
public final String name;
public final Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
// copy constructor (deep copy)
public Person(Person other) {
this.name = other.name;
this.address = other.address == null ? null : new Address(other.address.city);
}
}
4) Serialization-based deep copy (quick but slower)
Use when you want a simple one-liner deep copy for Serializable objects; performance and control are weaker than manual copying.
public static <T extends Serializable> T deepCopy(T obj) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos)) {
out.writeObject(obj);
out.flush();
try (ObjectInputStream in = new ObjectInputStream(
new ByteArrayInputStream(bos.toByteArray()))) {
@SuppressWarnings("unchecked")
T copy = (T) in.readObject();
return copy;
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("Deep copy failed", e);
}
}
5) Third-party utilities
- Apache Commons Lang SerializationUtils.clone(obj) for Serializable objects.
- Libraries that convert-to/from JSON can be used for deep copy when fields are JSON-friendly (but beware of type and performance issues).
Checklist / steps when implementing cloning
- Decide whether shallow copy is enough. If yes, implement clone() that calls super.clone() or provide a simple copy constructor.
- If deep copy is needed:
- Ensure mutable nested types are copied (via clone(), copy constructor, or creating new instances).
- Handle nulls and immutable fields appropriately.
- Prefer copy constructors or factory methods for clearer semantics.
Notes and pitfalls
- Object.clone() is a low-level mechanism and relies on a marker interface (Cloneable). Many Java developers prefer explicit copy constructors or factory methods because they are simpler and safer.
- Be careful with final fields and inheritance — clone() and super.clone() can interact oddly with immutability and constructors.
- Always document whether your copy is shallow or deep.
Quick summary
- Shallow copy: cheap, copies references; shared mutable state can cause bugs.
- Deep copy: independent copy of entire object graph; more work and costlier.
- Prefer explicit copy constructors/factories for maintainability; use serialization or libraries when appropriate for quick deep copies.
References / further reading
- Effective Java — prefer copy constructors or static factory methods over clone()
- Javadoc for java.lang.Object.clone()