Back to Blog
Java

Java Cloneable: How to Implement clone() Correctly

Understand Java's Cloneable marker interface, how to implement clone() correctly, avoid shallow-copy pitfalls, and choose safer alternatives like copy constructors.

Cloneableclone()Shallow CopyDeep CopyObject Copying
Diagram showing a Java object being cloned into a shallow copy and a deep copy with nested references.

To implement clone() correctly in Java, your class must implement the Cloneable marker interface, override clone() with public access, call super.clone() first, and then copy any mutable fields that should not be shared. The default Object.clone() behavior is a shallow copy, so the final step is usually the part that requires care.

Cloneable declares no methods. It is a runtime flag that allows Object.clone() to execute instead of throwing CloneNotSupportedException. The actual cloning logic lives in Object.clone(), which is protected and performs a shallow field-by-field copy. If a class does not implement Cloneable, calling clone() throws CloneNotSupportedException. This design is often criticized because it forces you to override clone() and make it public to expose copying, and shallow copy behavior is rarely what you want.

What Cloneable Actually Marks

Cloneable is a marker interface, meaning it has no methods. Its only purpose is to allow Object.clone() to execute without throwing CloneNotSupportedException. The Java runtime checks the interface at runtime, not at compile time. This is different from typical Java interfaces that define a contract via method signatures. The marker also does not guarantee that clone() is overridden or made public. A class can implement Cloneable and still have a protected clone() method inherited from Object, which means external code cannot call it directly unless it is in the same package or a subclass.

The practical effect is that implementing Cloneable is only half the work. You must also override clone() to make it accessible and to control the copying behavior. The interface itself provides no compile-time safety; it is purely a runtime flag.

The clone() Contract and Its Quirks

Object.clone() is protected native. It creates a new instance of the same class and copies each field's value to the new object. For primitive fields, that is a direct value copy. For reference fields, the reference itself is copied, not the referenced object. This is a shallow copy. The method returns an Object, so you must cast it to the appropriate type. It also throws CloneNotSupportedException if the object's class does not implement Cloneable.

The contract is subtle. The Javadoc for Object.clone() states that for any object x, the expression x.clone() != x is normally true, and x.clone().getClass() == x.getClass() is normally true, but these are not absolute requirements. Also, x.clone().equals(x) is not guaranteed. Object.clone() does not call any constructors. Constructor validation, resource initialization, and other side effects are skipped, so a clone may not be in the state that the constructor would have produced.

Implementing clone() Correctly

To use clone() on your own classes, you must override it and make it public. The typical implementation calls super.clone() and then fixes up any mutable fields that need an independent copy. Here is a minimal example:

import java.util.ArrayList; import java.util.List; public class Person implements Cloneable { private String name; private List<String> tags; public Person(String name, List<String> tags) { this.name = name; this.tags = new ArrayList<>(tags); } @Override public Person clone() { try { Person copy = (Person) super.clone(); copy.tags = new ArrayList<>(this.tags); return copy; } catch (CloneNotSupportedException e) { throw new AssertionError("Cloneable is implemented", e); } } }

The super.clone() call performs the shallow copy and returns an Object. You cast it to Person. Then you replace mutable reference fields with independent copies. In this case, tags is a List, so you create a new ArrayList from the original. The name field is a String, which is immutable, so a shallow copy is fine. Creating a new ArrayList copies the list structure, not the element objects; with String elements that is safe, but if tags contained mutable objects, you would need to copy those elements too for a deep copy.

The try-catch is necessary because Object.clone() declares CloneNotSupportedException, even though your class implements Cloneable. For this implementation, the exception cannot occur, so many developers wrap it in an AssertionError or a custom runtime exception. This is a common pattern.

Shallow vs Deep Copy: Where clone() Gets Dangerous

The default shallow copy is often insufficient. Consider a class that holds a reference to a mutable object like a Date, a Map, or a custom object. After a shallow copy, both the original and the clone share the same mutable reference. Modifying that reference through one object affects the other, which usually violates the expectation of a copy.

Copy typeBehavior for reference fieldsWhen to use
ShallowCopies the reference, shares the objectWhen fields are immutable or intentionally shared
DeepCopies the referenced object recursivelyWhen fields are mutable and must be independent

Deep copying is not automatic. You must manually copy each mutable field. For nested object graphs, this becomes tedious and error-prone. You also need to handle cycles and repeated references. If you accidentally leak a shared reference, you introduce subtle bugs that are hard to trace.

Common Pitfalls When Using Cloneable

One frequent mistake is forgetting to override clone() and expecting Cloneable to give you a public method. The interface alone does nothing. Another is ignoring the checked exception and letting it propagate, which forces callers to handle it even though it is effectively impossible.

A more subtle issue is that super.clone() bypasses constructors. If your constructor validates inputs, initializes resources, or sets up derived state, that logic is not repeated for the clone. The clone starts with the original's current field values, which may be inconsistent if the constructor did more than assign fields. For example, if a class caches a hash code in a field, the shallow copy will copy the cached value, but if the mutable fields change, the cache becomes stale.

Also, arrays have special behavior. The clone() method on arrays is public and returns a shallow copy of the array. For an array of primitives, that is a full copy. For an array of objects, it copies references. This is often a source of confusion. If you need an independent array of mutable objects, copy each element as well.

Alternatives to Cloneable: Copy Constructors and Factories

Given the pitfalls, many Java developers avoid Cloneable altogether. A copy constructor is a straightforward alternative:

import java.util.ArrayList; import java.util.List; public class Person { private final String name; private final List<String> tags; public Person(Person other) { this.name = other.name; this.tags = new ArrayList<>(other.tags); } }

This gives you compile-time safety, lets you use final fields, and does not require checked exceptions. You control exactly what is copied. A static factory method Person.copyOf(person) works similarly and can be named more clearly.

Serialization is a heavier alternative: it requires the class and all reachable objects to implement Serializable, and it can create a deep copy only by serializing and deserializing the object graph. That approach has security and performance costs. A field-by-field clone() avoids those stream costs, but it still needs manual fix-up for mutable fields. In modern Java, records can be a better fit when the data is immutable. You can pass an existing record's components to its canonical constructor to make a new instance, but records are not a general replacement for a mutable class that needs copying.

When Cloneable Is Still Worth Using

Despite its flaws, Cloneable is still present in legacy code and in some standard library classes. If you are working with an API that expects Cloneable objects, you may need to implement it. For example, Java's standard library classes such as ArrayList and HashMap implement Cloneable and expose a public clone(). In such cases, you can implement clone() correctly by following the pattern above.

For new code, a copy constructor or a factory method is almost always better. It is clearer, safer, and does not rely on a marker interface with runtime-only checks. If you control the class design, prefer those alternatives. If you must implement Cloneable for compatibility, document the copy semantics carefully and ensure that every mutable field is handled.

The decision comes down to whether you need to integrate with existing code that requires Cloneable. If not, the maintenance cost of overriding clone() and managing deep copies is rarely worth it. For most object copying needs, a well-written copy constructor is simpler and less surprising.

Java Cloneable: Implementing clone() Correctly | RYUSLOG DEV