Object Cloning in Java – clone(), Shallow and Deep Copies
Object cloning creates a copy of an existing object. Java's built-in mechanism is the clone() method inherited from Object, which works only for classes that implement the marker interface Cloneable. By default it makes a shallow copy: field values are copied, but referenced objects are shared.
This tutorial shows how to implement clone() correctly, explains shallow versus deep copies, and covers copy constructors, which are usually the better choice.
1. How clone() Works
Object.clone()isprotectedand declaresCloneNotSupportedException.- If the class implements
Cloneable, it allocates a new object of the same class and copies every field, without calling a constructor. - If not, it throws
CloneNotSupportedException.
public class NotCloneable {
static class Config {
String env = "dev";
Config copy() throws CloneNotSupportedException { return (Config) super.clone(); }
}
public static void main(String[] args) {
try {
new Config().copy();
} catch (CloneNotSupportedException e) {
System.out.println("Without Cloneable: " + e);
}
}
}
Without Cloneable: java.lang.CloneNotSupportedException: NotCloneable$Config
2. Implementing clone(): a Shallow Copy
The recommended pattern: implement Cloneable, override clone() as public with a covariant return type, call super.clone() and handle the impossible checked exception:
import java.util.ArrayList;
import java.util.List;
public class ShallowClone {
static class Student implements Cloneable {
String name;
int age;
List<String> courses = new ArrayList<>();
Student(String name, int age) { this.name = name; this.age = age; }
@Override
public Student clone() { // covariant return type, public
try {
return (Student) super.clone(); // field-by-field (shallow) copy
} catch (CloneNotSupportedException e) {
throw new AssertionError(e); // cannot happen: we implement Cloneable
}
}
public String toString() { return name + "(" + age + ") " + courses; }
}
public static void main(String[] args) {
Student original = new Student("Asha", 21);
original.courses.add("Java");
Student copy = original.clone();
copy.name = "Ravi"; // String reference replaced: independent
copy.age = 22; // primitive copied: independent
copy.courses.add("Spring"); // SAME list object: shared!
System.out.println("original: " + original);
System.out.println("copy: " + copy);
System.out.println("copy != original: " + (copy != original));
System.out.println("courses list shared (same)? " + (copy.courses == original.courses));
}
}
original: Asha(21) [Java, Spring] copy: Ravi(22) [Java, Spring] copy != original: true courses list shared (same)? true
Primitive fields and reassigned references are independent, but the courses list is the same object in both students. Adding a course through the copy changed the original too.
3. Deep Copy
A deep copy also copies the mutable objects that the object references, so the copy is fully independent:
import java.util.ArrayList;
import java.util.List;
public class DeepClone {
static class Student implements Cloneable {
String name;
List<String> courses = new ArrayList<>();
Student(String name) { this.name = name; }
@Override
public Student clone() {
try {
Student copy = (Student) super.clone();
copy.courses = new ArrayList<>(courses); // copy the mutable field too
return copy;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}
public static void main(String[] args) {
Student original = new Student("Asha");
original.courses.add("Java");
Student copy = original.clone();
copy.courses.add("Kafka");
System.out.println("original courses: " + original.courses);
System.out.println("copy courses: " + copy.courses);
}
}
original courses: [Java] copy courses: [Java, Kafka]
| Aspect | Shallow copy | Deep copy |
|---|---|---|
| Primitive fields | Copied | Copied |
| References to mutable objects | Shared with the original | Replaced by copies |
| Independence | Partial | Complete |
| Cost | Cheap | Depends on the object graph |
Default Object.clone() | Yes | No: must be implemented |
Immutable fields (String, Integer, LocalDate, records of immutable values) can be shared safely, so they never need deep copying.
4. Better Alternatives: Copy Constructors and Factories
A copy constructor or static copy factory is simpler and safer than clone(): no marker interface, no checked exception, no casts, and it works with final fields. Arrays are the exception where clone() is the idiomatic way to copy:
import java.util.ArrayList;
import java.util.List;
public class CopyConstructor {
// Preferred alternative: copy constructor / static factory, no Cloneable needed
static final class Order {
private final String id;
private final List<String> items;
Order(String id, List<String> items) { this.id = id; this.items = new ArrayList<>(items); }
Order(Order other) { this(other.id + "-copy", other.items); } // copy constructor
static Order copyOf(Order other) { return new Order(other); } // copy factory
void add(String item) { items.add(item); }
public String toString() { return id + " " + items; }
}
public static void main(String[] args) {
Order o1 = new Order("A-17", List.of("Keyboard"));
Order o2 = new Order(o1);
Order o3 = Order.copyOf(o1);
o2.add("Mouse");
System.out.println(o1);
System.out.println(o2);
System.out.println(o3);
int[] scores = {90, 85, 77};
int[] copy = scores.clone(); // arrays support clone() directly
copy[0] = 0;
System.out.println("array original[0] = " + scores[0] + ", copy[0] = " + copy[0]);
}
}
A-17 [Keyboard] A-17-copy [Keyboard, Mouse] A-17-copy [Keyboard] array original[0] = 90, copy[0] = 0
The JDK follows this style: new ArrayList<>(otherList), List.copyOf(...), new HashMap<>(otherMap).
5. Why clone() Is Considered Broken
Cloneablehas noclone()method, so you cannot clone through aCloneablereference.- It creates objects without running constructors, bypassing their validation.
- It conflicts with
finalfields that need deep copies, because they cannot be reassigned aftersuper.clone(). - Every class in a hierarchy must implement it correctly, or subclasses break.
- The checked
CloneNotSupportedExceptionclutters code.
Effective Java recommends copy constructors or factories except for arrays.
6. Summary
clone()works only for classes implementingCloneable; otherwise it throwsCloneNotSupportedException.- The default clone is a shallow copy: referenced objects are shared.
- Deep copies must copy mutable referenced objects explicitly.
- Prefer copy constructors or static factories; use
clone()for arrays.
Related Tutorials
Frequently Asked Questions (FAQ)
Q1. What is object cloning in Java?
Creating a copy of an object, typically using the clone() method inherited from Object.
Q2. Why must a class implement Cloneable?
Object.clone() checks for the Cloneable marker and throws CloneNotSupportedException if it is missing.
Q3. Does clone() call the constructor?
No. It copies the object's memory field by field without running any constructor.
Q4. What is a shallow copy?
A copy whose fields hold the same values as the original; references point to the same objects, so mutable referenced objects are shared.
Q5. What is a deep copy?
A copy in which mutable referenced objects are also copied, so changes to the copy never affect the original.
Q6. Is Object.clone() shallow or deep?
Shallow.
Q7. Why is clone() protected in Object?
So classes must explicitly opt in by overriding it, usually as public, rather than every object being cloneable from outside.
Q8. What is CloneNotSupportedException?
A checked exception thrown by Object.clone() when the object's class does not implement Cloneable.
Q9. Can an overriding clone() return a subclass type?
Yes. Covariant return types let Student clone() override Object clone(), avoiding casts for callers.
Q10. How do I clone an array?
Call array.clone(). It is public for arrays and returns the same array type. For arrays of objects it is a shallow copy.
Q11. What is a copy constructor?
A constructor that takes an object of the same class and initialises the new object from it, such as new Order(otherOrder).
Q12. Why prefer copy constructors to clone()?
They are simpler, need no marker interface or checked exception, run normal validation, and work with final fields.
Q13. Do I need to deep-copy String fields?
No. Strings are immutable, so sharing them between the original and copy is safe.
Q14. Can final fields be deep-copied in clone()?
Not directly, because final fields cannot be reassigned after super.clone(). This is a key limitation of clone().
Q15. Is clone() thread-safe?
Not inherently. If other threads modify the object during cloning, the copy may be inconsistent; synchronize if needed.
Q16. Do collections support clone()?
ArrayList, HashMap and others implement it as a shallow copy, but copy constructors such as new ArrayList<>(list) are clearer.
Q17. Can records be cloned?
Records do not implement Cloneable by default, and since they are immutable you rarely need copies; create a new record with modified components instead.
Q18. How can I deep-copy a complex object graph?
Write copy constructors for each class, or serialize and deserialize (simple but slow and requires serializability), or use a mapping library.
Q19. Does the clone share the same hashCode?
It depends on hashCode(). With the default identity hash code, the clone (a different object) usually has a different value; with a value-based hashCode, equal state gives equal hashes.
Q20. Does a subclass need to implement Cloneable if the parent does?
No. Cloneable is inherited, but the subclass may need to override clone() to deep-copy its own mutable fields.