Back to Blog
Java

Java Clone Method: How to Use It Correctly

Learn how the java clone method works, why shallow copies often fail, and how to implement deep copying correctly or choose safer alternatives like copy constructors.

JavaCloneableObject CloningDeep CopyCopy Constructor
Diagram showing a Java object being cloned into a shallow copy and a deep copy, with references to mutable objects highlighted.

The java clone method is defined in Object as a protected native method that creates a shallow copy of an object. Its behavior is conditional: it only works if the class implements the Cloneable marker interface. If not, it throws CloneNotSupportedException. Many developers misuse it because they assume it performs a deep copy, which it does not. This article explains the mechanics of clone(), how to override it properly, and why modern Java code often avoids it in favor of copy constructors or factory methods.

How Object.clone() Works

Object.clone() is a native method, meaning its implementation is platform-specific. It allocates memory for a new object and copies the bitwise contents of the original object's fields. For primitive fields, this directly copies values. For reference fields, it copies the reference, so the new object points to the same objects as the original. That is a shallow copy.

The method is protected, so it can only be called from within the class itself or from subclasses. To make it accessible, you must override it and make it public. The override should call super.clone() to leverage the native copying logic.

public class Employee implements Cloneable { private String name; private int id; @Override public Employee clone() throws CloneNotSupportedException { return (Employee) super.clone(); } }

The cast to Employee is safe because super.clone() returns an object of the runtime class of the original instance. The method signature uses a covariant return type, so callers can avoid an explicit cast.

Implementing clone() Correctly

The standard pattern for overriding clone() involves three steps: implement Cloneable, call super.clone(), and handle the checked exception. The exception is rarely thrown in practice because you have already verified that the class implements Cloneable, but the compiler requires it.

@Override public Employee clone() { try { return (Employee) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); // Cannot happen since we implement Cloneable } }

Wrapping the exception in an AssertionError is a common idiom because the condition is impossible if the class correctly implements Cloneable. However, some codebases prefer to propagate a runtime exception like RuntimeException to avoid adding throws to every caller.

The override must be public to be callable from outside the class. It should also be synchronized if the original class uses synchronization, though this is rarely necessary for a clone operation.

Shallow Copy vs Deep Copy

A shallow copy is sufficient when the object contains only primitive fields or immutable references. For example, a class with String and int fields can safely use the default clone() because String is immutable. But if the class contains mutable objects, the cloned object will share those references, leading to unintended side effects.

public class Department implements Cloneable { private String name; private List<Employee> employees; @Override public Department clone() { try { return (Department) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); } } }

Here, employees is a mutable list. After cloning, both the original and the clone refer to the same ArrayList instance. Adding an employee to one affects the other. This is rarely the intended behavior.

A deep copy duplicates all mutable objects referenced by the original. To implement it, you must manually copy each mutable field. For collections, you can create a new collection and copy elements, but the elements themselves may also need to be cloned if they are mutable.

@Override public Department clone() { try { Department copy = (Department) super.clone(); copy.employees = new ArrayList<>(employees.size()); for (Employee e : employees) { copy.employees.add(e.clone()); } return copy; } catch (CloneNotSupportedException e) { throw new AssertionError(); } }

This requires that Employee also implements Cloneable and overrides clone() properly. Deep copying becomes tedious when the object graph is complex, and it is easy to miss a field.

Common Pitfalls with clone()

Several subtle issues arise when using clone():

  • Final fields: If a field is final, you cannot reassign it in the clone. The native super.clone() copies the value, but if you need to deep-copy a final reference, you cannot replace it without reflection or a different approach.
  • Subclassing: If a superclass implements clone(), subclasses must be careful. The superclass's clone() may not account for fields added in the subclass. The subclass must override clone() and call super.clone() to ensure all fields are copied.
  • Arrays: Arrays have a built-in clone() method that performs a shallow copy. For an array of primitives, this is fine. For an array of objects, you get a new array but the elements are shared. A deep copy requires copying the array and then cloning each element.
  • Singletons and enums: Cloning can break singleton patterns. Enums are immune because their clone() method is final and throws CloneNotSupportedException, but regular singletons can be cloned unless you override clone() to throw an exception or return the same instance.

These pitfalls make clone() error-prone. A single missed field can introduce a subtle bug that only appears under specific conditions.

Alternatives to clone()

Because of these difficulties, many Java developers prefer other ways to copy objects. The most common alternatives are copy constructors and static factory methods.

A copy constructor takes an instance of the same class and creates a new object with the same state:

public Employee(Employee other) { this.name = other.name; this.id = other.id; }

For a deep copy, you explicitly copy mutable fields:

public Department(Department other) { this.name = other.name; this.employees = new ArrayList<>(); for (Employee e : other.employees) { this.employees.add(new Employee(e)); } }

Copy constructors are type-safe, do not require casting, and avoid the Cloneable marker. They also work with final fields because you assign them in the constructor. This approach is more verbose but far more predictable.

Another option is to use serialization to create a deep copy, but this is costly and requires the class to implement Serializable. It also bypasses constructors and can break if the object graph contains non-serializable objects. It is generally not recommended for routine copying.

Performance and Maintainability Considerations

Object.clone() is a native method, so it can be faster than a constructor-based copy because it avoids constructor execution and field-by-field assignment. However, this performance advantage is often negligible compared to the cost of deep copying collections or complex graphs. The real cost is in maintainability: clone() is difficult to get right, and the shallow-copy default is a constant source of bugs.

When you override clone(), you must update it every time you add a mutable field. If you forget, the clone silently shares that field. Copy constructors have the same maintenance burden, but they are more explicit and easier to test. The compiler can help with constructor calls, but it cannot detect a missing deep copy in clone().

In modern Java, clone() is largely considered a legacy API. The Java Language Specification itself notes that Cloneable is a strange interface because it does not declare any methods. Effective Java (by Joshua Bloch) recommends avoiding clone() and using copy constructors or factories instead. The only place clone() is still common is in array copying, where the built-in array clone is convenient and safe for primitives.

Choosing Between clone() and Copy Constructors

The decision depends on the context. If you are working with an existing codebase that already uses Cloneable, you may need to follow that pattern. For new code, prefer copy constructors or static factory methods. They are clearer, more robust, and do not require the Cloneable marker.

If you must use clone(), limit it to classes with only primitive or immutable fields. For anything else, implement a deep copy manually or use a copy constructor. Also, consider using Objects.requireNonNull or other validation in the copy constructor to ensure the source object is valid.

Here is a final example that shows a copy constructor for a class with a mutable list, which is a common real-world scenario:

public class Project { private final String name; private final List<Task> tasks; public Project(Project other) { this.name = other.name; this.tasks = new ArrayList<>(); for (Task t : other.tasks) { this.tasks.add(new Task(t)); } } // other constructors and methods }

This approach works with final fields, avoids the CloneNotSupportedException ceremony, and gives you full control over the copying process. It is the recommended way to copy objects in Java today.

java clone method: Practical Usage and Code Examples | RYUSLOG DEV