Java Cloneable: How to Implement clone() Correctly
Learn how java cloneable works, how to implement clone() correctly, avoid shallow copy pitfalls, and choose better alternatives.
java cloneable requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
The Cloneable interface in Java is a marker interface that signals to the JVM that an object's clone() method can be called. It does not declare any methods. 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 the 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 JVM 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 will be true, and x.clone().getClass() == x.getClass() will be true. However, x.clone().equals(x) is not guaranteed. The default implementation does not call any constructors, which means fields initialized in constructors are not re-run. This can lead to objects that are not fully initialized if the constructor performs side effects.
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 a deep copy. Here is a minimal example:
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 deep 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.
The try-catch is necessary because Object.clone() declares CloneNotSupportedException, even though your class implements Cloneable. Since the exception cannot occur in practice, 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 type | Behavior for reference fields | When to use |
|---|---|---|
| Shallow | Copies the reference, shares the object | When fields are immutable or intentionally shared |
| Deep | Copies the referenced object recursively | When fields are mutable and must be independent |
Deep copying is not automatic. You must manually clone 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 skipped during cloning. The cloned object may be in an inconsistent state. 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.
Alternatives to Cloneable: Copy Constructors and Factories
Given the pitfalls, many Java developers avoid Cloneable altogether. A copy constructor is a straightforward alternative:
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.
Another alternative is serialization, but that is heavy and slow, and it requires the class to implement Serializable. It also creates a deep copy only if you explicitly copy the object graph, but it has its own security and performance costs. The default clone() is faster than serialization because it avoids reflection and stream overhead, but it still copies fields one by one. In modern Java, you might also use a record, which provides a copy constructor via the canonical constructor, but records are immutable and not suitable for all cases.
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, some collection implementations use clone() internally. 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.