Java Tutorial
🔍

Java Object Cloning

Java Object Cloning

Object cloning means creating a second object that starts out with the same field values as a first one - but is a genuinely separate object, so changes to one do not affect the other. Java has a built-in mechanism for this: every object inherits a clone() method from Object, and a class can implement the Cloneable marker interface to make that method usable. Almost every experienced Java developer will, in the same breath, tell you this mechanism exists and tell you not to use it - clone() is widely regarded as one of the more poorly designed corners of the platform, and understanding exactly why is as important as knowing how it works.

What Is Object Cloning?

Object.clone() creates a new object of the same class as this, with every field copied from this into the new object - by default, a shallow copy: primitive fields are copied by value, but fields that are references to other objects are copied as references, so the original and the clone end up pointing to the same referenced objects.

ShoppingCart original = new ShoppingCart("Ananya", itemsList);
ShoppingCart copy = original.clone();   // shallow copy by default

original.customerName  ----+
copy.customerName       ---+--> "Ananya"   (String - shared, but immutable, so harmless)

original.items  --------+
copy.items        -------+--> [the SAME ArrayList object]
                              (mutable - shared, and NOT harmless)

Cloneable itself is unusual: it declares no methods at all. Implementing it does exactly one thing - it changes what Object.clone() does when called on an instance of the class. Without Cloneable, calling clone() (via super.clone()) throws CloneNotSupportedException. With it, super.clone() performs the field-by-field copy described above.

Basic Overview - Shallow Copy, Deep Copy, and the Alternative

SHALLOW COPY (Object.clone()'s default behavior)
  Fresher view  : the copy gets its own copies of simple values
                  (numbers, booleans, Strings), but if a field is a
                  List, Map, or other object, BOTH the original and
                  the copy end up pointing to the SAME inner object
  Deeper view   : this is a raw, field-by-field bitwise copy at the
                  JVM level - it does NOT call any constructor, and
                  does not know or care which fields hold references
                  to mutable objects

DEEP COPY (something YOU write, Java does not do this automatically)
  Fresher view  : a copy where the "inner objects" are copied too -
                  so the original and the copy are TRULY independent,
                  all the way down
  Deeper view   : achieved by overriding clone(), calling
                  super.clone() for the shallow copy, and then
                  explicitly replacing each mutable field with a
                  fresh copy of its contents

THE Cloneable MARKER INTERFACE
  Fresher view  : a class must "opt in" to cloning by writing
                  "implements Cloneable" - even though Cloneable has
                  no methods to actually implement
  Deeper view   : Cloneable affects ONLY what Object.clone() does
                  internally (shallow-copy vs. throw). It does NOT
                  declare clone() as public, does NOT require a class
                  to override it, and provides NO help with deep
                  copying - all of that is left entirely to the class

THE ALTERNATIVE MOST CODE ACTUALLY USES
  Fresher view  : many classes provide a COPY CONSTRUCTOR instead -
                  "new ShoppingCart(otherCart)" - which is often
                  simpler to read and write than clone()
  Deeper view   : copy constructors (and static "copyOf" factory
                  methods) avoid Cloneable, CloneNotSupportedException,
                  and the cast that clone() requires entirely - and,
                  unlike clone(), they can freely set final fields,
                  because constructors are allowed to do that

A fresher mainly needs to recognize the shallow-copy trap - "the copy shares a list with the original" is the single most common surprise this topic produces, and it shows up the first time someone clones an object and then mutates a collection field on the copy. The Cloneable/clone() mechanism itself, and exactly why it is considered broken, is where this topic becomes a genuine interview staple - not because anyone should use it often, but because understanding its flaws is how you recognize when a copy constructor is the better choice, which is most of the time.

Why Object Cloning Matters - and Why It's Controversial

The legitimate need behind cloning is real: sometimes code needs an independent working copy of a mutable object - to modify without affecting the original, to use as a "before" snapshot for an undo feature, or to take a pre-configured template and customize one copy of it per use (the Prototype design pattern, covered in this article's real-world example). Tying back to the immutable-class discussion: an immutable object never needs this - sharing is always safe when nothing can change. Cloning exists specifically for the mutable case.

Where clone() runs into trouble is almost entirely about how Java implemented the mechanism, not the idea. Joshua Bloch's Effective Java lists the problems in detail, and they compound:

Object.clone() is declared protected - a class that wants callers outside its own package to clone its instances must override clone() and widen it to public, which is easy to forget. Cloneable declares no clone() method, so implementing it provides no compile-time signal that a class actually supports cloning the way callers expect - a class can implement Cloneable and still throw at runtime if its override is missing or wrong. The default shallow copy silently shares mutable fields between original and clone, which is rarely what anyone wants and is easy to miss in testing if the shared field isn't mutated until later. clone() throws the checked CloneNotSupportedException - which, once a class implements Cloneable, can never actually occur from super.clone(), but the checked-exception signature forces a try-catch (or a throws declaration) anyway. And, as the internal working section below covers, clone() does not call any constructor - which means a class with final fields that need deep-copying often cannot implement deep cloning via clone() at all, because reassigning a final field is only legal inside a constructor.

None of this means clone() is unusable - array cloning, in particular, is genuinely fine and idiomatic. It means that for ordinary classes, the question "should this class be Cloneable" is, for most experienced developers, almost always answered "no - give it a copy constructor instead."

How It Works

The Shallow Copy Problem

The class below implements Cloneable and overrides clone() - correctly, by the letter of the API - by simply returning super.clone(). This compiles, runs without exception, and produces a copy that looks identical to the original. The problem only appears once the copy is used.

1// File: ShallowCloneDemo.java 2 3import java.util.ArrayList; 4import java.util.List; 5 6public class ShallowCloneDemo { 7 8 static class ShoppingCart implements Cloneable { 9 private String customerName; 10 private List<String> items; 11 12 ShoppingCart(String customerName, List<String> items) { 13 this.customerName = customerName; 14 this.items = items; 15 } 16 17 void addItem(String item) { 18 items.add(item); 19 } 20 21 @Override 22 public ShoppingCart clone() { 23 try { 24 // super.clone() performs a SHALLOW, field-by-field copy - 25 // 'items' in the clone points to the SAME List object 26 // as 'items' in the original 27 return (ShoppingCart) super.clone(); 28 } catch (CloneNotSupportedException e) { 29 throw new AssertionError(e); // cannot happen - we ARE Cloneable 30 } 31 } 32 33 @Override 34 public String toString() { 35 return "ShoppingCart[customer=" + customerName + ", items=" + items + "]"; 36 } 37 } 38 39 public static void main(String[] args) { 40 ShoppingCart original = new ShoppingCart("Ananya", new ArrayList<>(List.of("Notebook", "Pen"))); 41 ShoppingCart copy = original.clone(); 42 43 System.out.println("=== Right after cloning ==="); 44 System.out.println("original: " + original); 45 System.out.println("copy : " + copy); 46 System.out.println("Same items reference? " + (original.items == copy.items)); 47 48 System.out.println(); 49 50 System.out.println("=== Adding an item to the COPY's cart ==="); 51 copy.addItem("Eraser"); 52 System.out.println("original: " + original); 53 System.out.println("copy : " + copy); 54 System.out.println("Adding to the copy also changed the original - they SHARE the same List"); 55 } 56}
Output:
=== Right after cloning ===
original: ShoppingCart[customer=Ananya, items=[Notebook, Pen]]
copy    : ShoppingCart[customer=Ananya, items=[Notebook, Pen]]
Same items reference? true

=== Adding an item to the COPY's cart ===
original: ShoppingCart[customer=Ananya, items=[Notebook, Pen, Eraser]]
copy    : ShoppingCart[customer=Ananya, items=[Notebook, Pen, Eraser]]
Adding to the copy also changed the original - they SHARE the same List

Deep Copy by Cloning Mutable Fields

Fixing this requires the override to do more than call super.clone() - after the shallow copy, every field that refers to a mutable object needs to be replaced with a fresh copy of that object's contents.

1// File: DeepCloneDemo.java 2 3import java.util.ArrayList; 4import java.util.List; 5 6public class DeepCloneDemo { 7 8 static class ShoppingCart implements Cloneable { 9 private String customerName; 10 private List<String> items; 11 12 ShoppingCart(String customerName, List<String> items) { 13 this.customerName = customerName; 14 this.items = items; 15 } 16 17 void addItem(String item) { 18 items.add(item); 19 } 20 21 @Override 22 public ShoppingCart clone() { 23 try { 24 ShoppingCart copy = (ShoppingCart) super.clone(); 25 // DEEP COPY STEP - replace the shared 'items' reference 26 // with a NEW ArrayList containing the same elements. 27 // This reassignment is only possible because 'items' 28 // is NOT declared final. 29 copy.items = new ArrayList<>(this.items); 30 return copy; 31 } catch (CloneNotSupportedException e) { 32 throw new AssertionError(e); 33 } 34 } 35 36 @Override 37 public String toString() { 38 return "ShoppingCart[customer=" + customerName + ", items=" + items + "]"; 39 } 40 } 41 42 public static void main(String[] args) { 43 ShoppingCart original = new ShoppingCart("Ananya", new ArrayList<>(List.of("Notebook", "Pen"))); 44 ShoppingCart copy = original.clone(); 45 46 System.out.println("Same items reference? " + (original.items == copy.items)); 47 48 System.out.println(); 49 50 System.out.println("=== Adding an item to the COPY's cart ==="); 51 copy.addItem("Eraser"); 52 System.out.println("original: " + original); 53 System.out.println("copy : " + copy); 54 System.out.println("Original is UNCHANGED - the clone has its own independent List"); 55 } 56}
Output:
Same items reference? false

=== Adding an item to the COPY's cart ===
original: ShoppingCart[customer=Ananya, items=[Notebook, Pen]]
copy    : ShoppingCart[customer=Ananya, items=[Notebook, Pen, Eraser]]
Original is UNCHANGED - the clone has its own independent List

Array Cloning and the Copy Constructor Alternative

Arrays are the one place clone() is genuinely idiomatic - every array has a public clone() method, with no Cloneable ceremony required. For primitive arrays, the result is fully independent. For object arrays, clone() is still shallow - the new array's slots hold the same element references as the original - but this is often harmless when the elements are themselves immutable. The same demo also shows the copy-constructor pattern most classes should use instead of Cloneable.

1// File: ArrayCloneAndCopyConstructorDemo.java 2 3import java.util.ArrayList; 4import java.util.List; 5 6public class ArrayCloneAndCopyConstructorDemo { 7 8 public static void main(String[] args) { 9 10 System.out.println("=== Primitive array clone() - genuinely independent ==="); 11 int[] originalScores = {85, 90, 78}; 12 int[] clonedScores = originalScores.clone(); 13 clonedScores[0] = 100; 14 System.out.println("originalScores: " + java.util.Arrays.toString(originalScores)); 15 System.out.println("clonedScores : " + java.util.Arrays.toString(clonedScores)); 16 17 System.out.println(); 18 19 System.out.println("=== Object array clone() - shallow, harmless here since Strings are immutable ==="); 20 String[] originalNames = {"Ananya", "Rahul"}; 21 String[] clonedNames = originalNames.clone(); 22 clonedNames[0] = "Priya"; // reassigns the SLOT - does not mutate "Ananya" itself 23 System.out.println("originalNames: " + java.util.Arrays.toString(originalNames)); 24 System.out.println("clonedNames : " + java.util.Arrays.toString(clonedNames)); 25 26 System.out.println(); 27 28 System.out.println("=== Copy constructor - the recommended alternative to clone() ==="); 29 ShoppingCart original = new ShoppingCart("Ananya", new ArrayList<>(List.of("Notebook", "Pen"))); 30 ShoppingCart copy = new ShoppingCart(original); // copy constructor 31 32 copy.addItem("Eraser"); 33 System.out.println("original: " + original); 34 System.out.println("copy : " + copy); 35 System.out.println("Same result as deep clone - no Cloneable, no cast, no checked exception"); 36 } 37 38 static class ShoppingCart { 39 private String customerName; 40 private List<String> items; 41 42 ShoppingCart(String customerName, List<String> items) { 43 this.customerName = customerName; 44 this.items = new ArrayList<>(items); // defensive copy on input 45 } 46 47 // COPY CONSTRUCTOR - takes another ShoppingCart, builds an 48 // independent copy. No Cloneable, no super.clone(), no cast, 49 // no CloneNotSupportedException. 50 ShoppingCart(ShoppingCart other) { 51 this.customerName = other.customerName; 52 this.items = new ArrayList<>(other.items); 53 } 54 55 void addItem(String item) { 56 items.add(item); 57 } 58 59 @Override 60 public String toString() { 61 return "ShoppingCart[customer=" + customerName + ", items=" + items + "]"; 62 } 63 } 64}
Output:
=== Primitive array clone() - genuinely independent ===
originalScores: [85, 90, 78]
clonedScores  : [100, 90, 78]

=== Object array clone() - shallow, harmless here since Strings are immutable ===
originalNames: [Ananya, Rahul]
clonedNames  : [Priya, Rahul]

=== Copy constructor - the recommended alternative to clone() ===
original: ShoppingCart[customer=Ananya, items=[Notebook, Pen]]
copy    : ShoppingCart[customer=Ananya, items=[Notebook, Pen, Eraser]]
Same result as deep clone - no Cloneable, no cast, no checked exception

What super.clone() Actually Does Internally

super.clone() (Object.clone(), a native method) DOES THE FOLLOWING:
  1. Allocates a NEW object of the SAME RUNTIME CLASS as 'this' -
     the actual class, even if 'this' is referenced through a
     superclass type
  2. Copies every field's VALUE from 'this' into the new object,
     field by field - primitives copied by value, references copied
     AS REFERENCES (both objects then point to the SAME referenced object)
  3. Returns the new object, typed as Object - the caller casts it

  NO CONSTRUCTOR RUNS. This is a raw, field-by-field copy - not
  "new ShoppingCart(...)". Any logic in ShoppingCart's constructors
  (validation, defensive copies, derived field setup) is SKIPPED
  entirely for the clone.

WHY final FIELDS BREAK DEEP CLONING:
  super.clone() copies a final field's CURRENT VALUE into the clone
  just fine - the raw copy does not check 'final' at all.
  BUT if that field holds a reference to something that needs deep
  copying, the override must do:
      copy.items = new ArrayList<>(this.items);
  This is a NORMAL field assignment in source code - and the JLS
  permits assigning a final INSTANCE field only from within a
  CONSTRUCTOR of its class. clone() is an ordinary method, not a
  constructor, so this line is a COMPILE ERROR if 'items' is final.
  A class with a final mutable field cannot deep-copy that field via
  the override-clone()-and-fix-up pattern at all.

WHY Cloneable IS CALLED "A MARKER INTERFACE GONE WRONG":
  Cloneable declares NO methods. Implementing it changes exactly ONE
  thing: what Object.clone() does when called via super.clone() -
  shallow-copy instead of throwing CloneNotSupportedException. It does
  NOT require, declare, or provide a PUBLIC clone() method - every
  class that wants one must write its own override, by convention,
  with nothing in Cloneable enforcing that this happens correctly.

Real-World Example - CRED Reward Campaign Templates

A rewards platform configures a base campaign template - default reward points, eligible user segments, and message variables - then produces several campaign-specific variants from it. Each variant needs to start from the same baseline and be customized independently, without any variant's changes leaking into the base template or into each other. This is the Prototype pattern: the base template is a prototype, and each campaign is a customized clone of it.

1// File: CampaignTemplate.java 2 3import java.util.ArrayList; 4import java.util.LinkedHashMap; 5import java.util.List; 6import java.util.Map; 7 8public class CampaignTemplate implements Cloneable { 9 10 private String campaignName; 11 private int rewardPoints; 12 private List<String> eligibleSegments; 13 private Map<String, String> messageVariables; 14 15 public CampaignTemplate(String campaignName, int rewardPoints, 16 List<String> eligibleSegments, Map<String, String> messageVariables) { 17 this.campaignName = campaignName; 18 this.rewardPoints = rewardPoints; 19 this.eligibleSegments = new ArrayList<>(eligibleSegments); 20 this.messageVariables = new LinkedHashMap<>(messageVariables); 21 } 22 23 public void setCampaignName(String campaignName) { this.campaignName = campaignName; } 24 public void setRewardPoints(int rewardPoints) { this.rewardPoints = rewardPoints; } 25 public void addEligibleSegment(String segment) { eligibleSegments.add(segment); } 26 public void setMessageVariable(String key, String value) { messageVariables.put(key, value); } 27 28 @Override 29 public CampaignTemplate clone() { 30 try { 31 CampaignTemplate copy = (CampaignTemplate) super.clone(); 32 // Deep-copy BOTH mutable fields - the clone gets its OWN 33 // List and Map, independent of this template's 34 copy.eligibleSegments = new ArrayList<>(this.eligibleSegments); 35 copy.messageVariables = new LinkedHashMap<>(this.messageVariables); 36 return copy; 37 } catch (CloneNotSupportedException e) { 38 throw new AssertionError(e); 39 } 40 } 41 42 @Override 43 public String toString() { 44 return "CampaignTemplate[" + campaignName + ", points=" + rewardPoints 45 + ", segments=" + eligibleSegments + ", variables=" + messageVariables + "]"; 46 } 47}
1// File: CampaignTemplateDemo.java 2 3import java.util.List; 4import java.util.Map; 5 6public class CampaignTemplateDemo { 7 8 public static void main(String[] args) { 9 10 // ONE base template - configured once, treated as a PROTOTYPE 11 CampaignTemplate baseTemplate = new CampaignTemplate( 12 "Base Reward Campaign", 13 100, 14 List.of("ALL_USERS"), 15 Map.of("brandName", "CRED") 16 ); 17 18 System.out.println("=== Base template ==="); 19 System.out.println(baseTemplate); 20 21 System.out.println(); 22 23 System.out.println("=== Cloning the base template for two different campaigns ==="); 24 CampaignTemplate diwaliCampaign = baseTemplate.clone(); 25 diwaliCampaign.setCampaignName("Diwali Bonus"); 26 diwaliCampaign.setRewardPoints(500); 27 diwaliCampaign.addEligibleSegment("ACTIVE_LAST_30_DAYS"); 28 diwaliCampaign.setMessageVariable("festival", "Diwali"); 29 30 CampaignTemplate referralCampaign = baseTemplate.clone(); 31 referralCampaign.setCampaignName("Referral Bonus"); 32 referralCampaign.setRewardPoints(250); 33 referralCampaign.addEligibleSegment("HAS_REFERRED_FRIEND"); 34 35 System.out.println(diwaliCampaign); 36 System.out.println(referralCampaign); 37 38 System.out.println(); 39 40 System.out.println("=== Base template is UNCHANGED ==="); 41 System.out.println(baseTemplate); 42 } 43}
Output:
=== Base template ===
CampaignTemplate[Base Reward Campaign, points=100, segments=[ALL_USERS], variables={brandName=CRED}]

=== Cloning the base template for two different campaigns ===
CampaignTemplate[Diwali Bonus, points=500, segments=[ALL_USERS, ACTIVE_LAST_30_DAYS], variables={brandName=CRED, festival=Diwali}]
CampaignTemplate[Referral Bonus, points=250, segments=[ALL_USERS, HAS_REFERRED_FRIEND], variables={brandName=CRED}]

=== Base template is UNCHANGED ===
CampaignTemplate[Base Reward Campaign, points=100, segments=[ALL_USERS], variables={brandName=CRED}]

Both diwaliCampaign and referralCampaign start as deep copies of baseTemplate - each gets its own eligibleSegments list and messageVariables map, from the clone() override's two reassignment lines. Every customization made to either campaign afterward - new segments, new variables, changed point values - is isolated to that campaign alone, and baseTemplate remains exactly as it was configured, ready to be cloned again for the next campaign.

Object Cloning vs Copy Constructor vs Static Copy Factory

Aspectclone() / CloneableCopy ConstructorStatic Copy Factory
Requires a marker interfaceYes - CloneableNoNo
Default behaviorShallow, field-by-field, no constructor runsWhatever the constructor body does - typically deep, via ordinary field assignmentWhatever the method body does
Works with final fields needing deep copyNo - cannot reassign a final field outside a constructorYes - constructors may assign final fields normallyYes
Exception ceremonyCloneNotSupportedException - must be caught even though, once Cloneable, it cannot occurNoneNone
Runtime type of the resultAlways the same class as this (super.clone() preserves the actual class)Caller chooses the target type explicitlyCaller chooses; can even return a different implementation of an interface
Effective Java guidanceGenerally avoidPreferredPreferred, especially for interface types ("conversion constructor/factory")

The pattern in this table's rows is consistent: every advantage clone() is sometimes credited with - "it's built in," "subclasses get it automatically" - comes with a corresponding row where a copy constructor or static factory simply does not have the problem in the first place. This is why the realistic guidance is rarely "never use Cloneable" as an absolute rule, but rather "a copy constructor solves this with less ceremony, so reach for Cloneable only when something genuinely requires it" - which, for application code, is rare.

Best Practices

Prefer a copy constructor or a static copyOf/of factory method over implementing Cloneable. new ShoppingCart(original) says exactly what it does, requires no marker interface, no checked exception, no cast - and, unlike clone(), can assign final fields normally, because constructors are allowed to.

If clone() must be implemented - a framework or legacy interface requires Cloneable - override it as public, with a covariant return type matching the actual class, and deep-copy every mutable field explicitly. ShoppingCart clone() (not Object clone()) lets callers use the result without a cast; the explicit deep-copy lines for every mutable field are the entire point of the override - without them, clone() is shallow by default, which is rarely correct.

Never rely on the default shallow clone() for a class with any mutable reference field. ShallowCloneDemo above compiles, runs, and produces a "copy" that looks correct right up until something mutates a shared field - exactly the kind of bug that survives a quick test and surfaces later, in production, when two parts of a system that should have been independent turn out not to be.

For arrays, know which kind of clone() you're getting. A primitive array's clone() is a genuine, independent deep copy - int[], double[], and similar are completely safe to .clone(). An object array's clone() is shallow - the new array's elements are the SAME objects as the original's. This is harmless when the element type is immutable (as with String[]) and a real bug when it is not (as with an array of mutable objects).

Common Mistakes

Mistake 1 - Relying on the Default Shallow clone() for a Mutable Field

1import java.util.ArrayList; 2import java.util.List; 3 4// WRONG - implements Cloneable, overrides clone(), but the override 5// is just "return super.clone()". 'items' is a List - the clone and 6// the original SHARE it. Mutating either one's cart affects both. 7class ShoppingCartBroken implements Cloneable { 8 private List<String> items; 9 10 ShoppingCartBroken(List<String> items) { this.items = items; } 11 12 void addItem(String item) { items.add(item); } 13 14 @Override 15 public ShoppingCartBroken clone() { 16 try { 17 return (ShoppingCartBroken) super.clone(); // SHALLOW - 'items' is shared 18 } catch (CloneNotSupportedException e) { 19 throw new AssertionError(e); 20 } 21 } 22} 23 24// CORRECT - explicitly deep-copy 'items' after the shallow super.clone() 25class ShoppingCartFixed implements Cloneable { 26 private List<String> items; 27 28 ShoppingCartFixed(List<String> items) { this.items = items; } 29 30 void addItem(String item) { items.add(item); } 31 32 @Override 33 public ShoppingCartFixed clone() { 34 try { 35 ShoppingCartFixed copy = (ShoppingCartFixed) super.clone(); 36 copy.items = new ArrayList<>(this.items); // deep copy 37 return copy; 38 } catch (CloneNotSupportedException e) { 39 throw new AssertionError(e); 40 } 41 } 42}

Mistake 2 - Calling super.clone() Without Implementing Cloneable

1// WRONG - 'Report' does NOT implement Cloneable. The override's 2// SIGNATURE compiles fine (Object.clone() throws 3// CloneNotSupportedException, so declaring it here is allowed) - but 4// calling clone() at RUNTIME throws CloneNotSupportedException, because 5// super.clone() checks "is 'this' an instanceof Cloneable" internally 6// and 'this' is not. 7class ReportBroken { 8 private String title; 9 10 ReportBroken(String title) { this.title = title; } 11 12 @Override 13 public ReportBroken clone() throws CloneNotSupportedException { 14 return (ReportBroken) super.clone(); // throws CloneNotSupportedException at runtime 15 } 16} 17 18// CORRECT - implement Cloneable 19class ReportFixed implements Cloneable { 20 private String title; 21 22 ReportFixed(String title) { this.title = title; } 23 24 @Override 25 public ReportFixed clone() { 26 try { 27 return (ReportFixed) super.clone(); 28 } catch (CloneNotSupportedException e) { 29 throw new AssertionError(e); // cannot happen - we ARE Cloneable 30 } 31 } 32}

Mistake 3 - Trying to Deep-Copy a final Mutable Field in clone()

1import java.util.ArrayList; 2import java.util.List; 3 4// WRONG - 'items' is final. super.clone() copies its current reference 5// into the clone fine - but the override then cannot REASSIGN 'items' 6// to a deep copy, because final instance fields can only be assigned 7// inside a CONSTRUCTOR, and clone() is not one. 8class CartBroken implements Cloneable { 9 private final String customerName; 10 private final List<String> items; 11 12 CartBroken(String customerName, List<String> items) { 13 this.customerName = customerName; 14 this.items = items; 15 } 16 17 @Override 18 public CartBroken clone() { 19 try { 20 CartBroken copy = (CartBroken) super.clone(); 21 copy.items = new ArrayList<>(this.items); // COMPILE ERROR 22 // "cannot assign a value to final variable items" 23 return copy; 24 } catch (CloneNotSupportedException e) { 25 throw new AssertionError(e); 26 } 27 } 28} 29 30// CORRECT - skip clone() entirely. A copy constructor CAN assign 31// final fields, because constructors are allowed to. 32class CartFixed { 33 private final String customerName; 34 private final List<String> items; 35 36 CartFixed(String customerName, List<String> items) { 37 this.customerName = customerName; 38 this.items = new ArrayList<>(items); 39 } 40 41 CartFixed(CartFixed other) { 42 this.customerName = other.customerName; 43 this.items = new ArrayList<>(other.items); 44 } 45}

Mistake 4 - Assuming Array clone() Deep-Copies Mutable Elements

1import java.util.ArrayList; 2import java.util.List; 3 4// WRONG - 'clonedCarts' is a DIFFERENT array object (array clone() 5// always returns a new array) - but its ELEMENTS are the SAME 6// ShoppingCart objects as in 'originalCarts'. Mutating a cart through 7// either array affects the one shared object. 8class ArrayCloneMistake { 9 static void demo(ShoppingCartFixed[] originalCarts) { 10 ShoppingCartFixed[] clonedCarts = originalCarts.clone(); 11 12 clonedCarts[0].addItem("Eraser"); // mutates the cart at index 0 - 13 // originalCarts[0] is the SAME object, so it now has "Eraser" too 14 } 15} 16 17// CORRECT - clone the ARRAY, then copy-construct (or deep-clone) each 18// element individually 19class ArrayCloneFixed { 20 static ShoppingCartFixed[] deepCopy(ShoppingCartFixed[] originalCarts) { 21 ShoppingCartFixed[] result = new ShoppingCartFixed[originalCarts.length]; 22 for (int i = 0; i < originalCarts.length; i++) { 23 result[i] = originalCarts[i].clone(); // or a copy constructor, per element 24 } 25 return result; 26 } 27}

Interview Questions

Q1. What is the difference between a shallow copy and a deep copy?

A shallow copy creates a new object whose primitive fields have independent values, but whose reference fields point to the SAME objects as the original's reference fields - the new object and the original share any mutable state reachable through those fields. A deep copy creates a new object where every reachable mutable object has also been copied, recursively, so the new object and the original share nothing mutable and are completely independent. Object.clone()'s default behavior, via super.clone(), is always a shallow copy; a deep copy requires the class's clone() override (or copy constructor) to explicitly copy each mutable field's contents.

Q2. What does Object.clone() actually do internally, and why does it skip constructors?

Object.clone() is a native method that allocates a new object of the same runtime class as this, then copies every field's value from this into the new object directly - primitives by value, references as references - and returns the result. It does not call any constructor of the class. This is by design: clone() is meant to reproduce the exact current state of an object, including any state that might not be reachable through any public constructor (an object's constructors might not accept every possible internal configuration as parameters). The cost of skipping constructors is that any constructor-time logic - validation, defensive copying, computing derived fields - is bypassed for the clone, which is part of why classes with such logic in their constructors often need substantial work in their clone() override to remain correct.

Q3. Why is Cloneable considered a poorly designed interface in Java?

Cloneable declares no methods, so implementing it provides no compile-time contract - a class can implement Cloneable and still have no usable public clone(), or a broken one, with nothing in the type system catching this. Its only effect is on Object.clone()'s internal behavior (shallow-copy vs. throw CloneNotSupportedException), which is an unusual way for an interface to work - normally, implementing an interface means providing its methods, not changing the behavior of an inherited method from a completely different type. Combined with clone() being protected by default (requiring every class to re-declare it public), the checked CloneNotSupportedException that can never actually be thrown once Cloneable is implemented, and the lack of any help with deep copying, the overall mechanism requires a great deal of careful, repetitive code to use correctly - which is exactly the kind of API that tends to be used incorrectly.

Q4. Why can't a class with final mutable fields easily support deep cloning via clone()?

super.clone() copies a final field's current value into the clone without issue - the raw, field-by-field copy does not check for final at all. The problem is in the override's next step: deep-copying that field requires reassigning it - copy.items = new ArrayList<>(this.items); - and the Java Language Specification only permits assigning a final instance field from within a constructor of its class. clone() is an ordinary method, not a constructor, so this reassignment is a compile error. A class with a final mutable field genuinely cannot perform this specific deep-copy pattern inside clone() - which is one of the more concrete reasons copy constructors (which CAN assign final fields, being constructors) are preferred.

Q5. What happens if clone() is called on a class that does not implement Cloneable?

If a class overrides clone() and that override calls super.clone(), and the class does NOT implement Cloneable, then super.clone() throws CloneNotSupportedException at runtime. This compiles without error - Object.clone()'s signature declares this checked exception, so any override declaring it (or catching it) is valid Java - but every call to clone() on such a class fails at runtime with this exception. This is a common source of confusion: the failure is not a compile-time signal that something is missing, but a runtime exception that only appears when clone() is actually called.

Q6. Why do Effective Java and most experienced developers recommend copy constructors over clone()?

A copy constructor - ShoppingCart(ShoppingCart other) - achieves the same goal as a correctly-implemented deep clone(), with none of clone()'s structural problems: no marker interface is needed, no checked exception that can never fire, no cast of the result, and no restriction on assigning final fields, since constructors are explicitly permitted to do that. A copy constructor also runs normal constructor logic - validation, defensive copying of its own inputs - consistently, the same way any other construction of the object would, rather than bypassing it the way clone() does. The only things clone() offers that a copy constructor does not are preserving the exact runtime class automatically for subclasses (useful in a narrow set of polymorphic-copying scenarios) and array cloning's built-in public clone() - and for ordinary application classes, neither of these typically outweighs the rest of the mechanism's costs.

FAQs

Is array.clone() a deep copy or a shallow copy?

For arrays of primitives (int[], double[], boolean[], and so on), clone() produces a fully independent copy - changing an element in the clone has no effect on the original, and vice versa, because primitive values are copied directly. For arrays of objects (String[], ShoppingCart[], and so on), clone() produces a new array object, but its slots hold the SAME object references as the original array's slots - it is shallow with respect to the elements. Whether this matters depends on whether the element type is itself mutable.

Does clone() call the class's constructor?

No. Object.clone() (via super.clone()) allocates the new object and copies field values directly, without invoking any constructor of the class - not the no-argument constructor, not any parameterized constructor. Any logic that normally runs during construction - validation, defensive copying of constructor arguments, computing derived fields - does not run for a clone unless the clone() override explicitly reproduces it.

Can a final class implement Cloneable?

Yes - final and Cloneable are unrelated. A final class can implement Cloneable and override clone() exactly as any other class would; final only prevents the class from being subclassed, which has no bearing on whether instances of the class itself can be cloned.

What is the Prototype design pattern, and how does it relate to clone()?

The Prototype pattern creates new objects by copying an existing, pre-configured "prototype" object, rather than constructing each new object from scratch with the same configuration repeated every time. clone() is one mechanism for implementing this pattern - a prototype object's clone() method produces a new, independent starting point that can then be customized, as CampaignTemplate.clone() does in this article's real-world example. A copy constructor can implement the same pattern equally well - the pattern is about the idea of copy-then-customize; clone() is just one possible mechanism for the "copy" step.

Does cloning copy static fields?

No - and this question is slightly malformed in a useful way: static fields belong to the CLASS, not to any instance, so there is only ever one copy of a static field's value regardless of how many instances (or clones) exist. clone() copies instance field values from one object to another; a static field's value is the same value seen by the original, the clone, and every other instance of the class, because there is exactly one of it.

Is clone() thread-safe?

clone() itself - the field-by-field copy performed by super.clone() - is not inherently synchronized, so if another thread is concurrently modifying the object being cloned, the clone could end up with a mix of old and new field values (a "torn" read), the same risk any unsynchronized read of mutable shared state carries. This is yet another argument in favor of immutable objects (which clone() is unnecessary for in the first place) or, when cloning a mutable object that is genuinely shared across threads, ensuring the clone operation itself happens under whatever synchronization protects that object's other accesses.

Summary

Object.clone(), enabled by implementing the empty Cloneable marker interface, performs a shallow, field-by-field copy of an object without calling any constructor - primitive fields end up independent, but reference fields end up shared between the original and the clone. Making a clone genuinely independent - a deep copy - requires the clone() override to explicitly replace every mutable field with a fresh copy of its contents, a step Object.clone() provides no help with and that final fields can structurally prevent, since reassignment outside a constructor is not legal.

Array cloning is the one place this mechanism is straightforwardly useful - primitive arrays clone deeply, object arrays clone their slots but not their elements, and no Cloneable ceremony is required either way.

For ordinary classes, the practical takeaway is the comparison table's conclusion: a copy constructor achieves everything a correct deep clone() would, without Cloneable, without the checked exception that can never fire, and without the final-field restriction - which is why, when a new class needs a "make me an independent copy of this" capability, a copy constructor is almost always the simpler and more robust choice, with clone() reserved for the cases - arrays, and APIs that specifically require Cloneable - where it is genuinely the right tool.

What to Read Next