Object Cloning in Java - Shallow Copy, Deep Copy and Safer Alternatives

⏱️ 24 min read • Intermediate Level • Lesson 62

Lesson 62 of 124 of Java Tutorial
You have completed this lesson

Completed on . You can revise this lesson or continue to the next topic.

Object cloning in Java creates a new object whose fields initially contain values copied from an existing object.

Java provides the protected Object.clone() method and the marker interface Cloneable. However, the default mechanism performs a shallow field copy, so mutable nested objects require special handling. Copy constructors and factory methods are often clearer alternatives.


What Is Object Cloning in Java?

Object cloning creates a separate object with field values copied from an existing object. The new object has a different identity, but whether nested objects are shared or copied depends on the copying strategy.

Simple Meaning: Cloning starts a new object from the current state of another object.
A successful clone normally has:
  • A different object identity from the original.
  • The same runtime class as the original when using super.clone().
  • Initially copied primitive and reference field values.
  • Independent mutable nested objects only when deep copying is implemented.

Why Clone Objects?

Create a Starting Copy

Build a new object from an existing configuration.

Protect the Original

Change a copy without intentionally changing the source object.

Snapshot State

Capture state for editing, undo, or comparison workflows.

Prototype Creation

Create similar objects from a prepared prototype.

Object.clone() Method

The clone() method is declared in java.lang.Object. It is protected and performs a field-by-field shallow copy when the object's class supports cloning.

Simplified Signature
protected native Object clone()
        throws CloneNotSupportedException;
Because Object.clone() is protected, a class commonly overrides it with a public method and a more specific return type.

Cloneable Interface

Cloneable is a marker interface. It declares no methods. Its presence tells Object.clone() that a field-by-field copy is permitted.

  • Implementing Cloneable does not automatically make clone() public.
  • Implementing it does not add a clone method to the interface contract.
  • Calling super.clone() without it causes CloneNotSupportedException.
  • It does not define shallow-copy or deep-copy semantics for mutable fields.

Basic Object Cloning Example

Employee.java
Copy Try Download
class Employee implements Cloneable {
    private int id;
    private String name;

    Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public Employee clone() {
        try {
            return (Employee) super.clone();
        } catch (CloneNotSupportedException exception) {
            throw new AssertionError(exception);
        }
    }

    @Override
    public String toString() {
        return id + " - " + name;
    }

    public static void main(String[] args) {
        Employee original = new Employee(101, "Ayan");
        Employee copy = original.clone();

        System.out.println(original);
        System.out.println(copy);
        System.out.println(original == copy);
    }
}

Output:

101 - Ayan
101 - Ayan
false

Explanation

  1. The class implements Cloneable.
  2. The public override calls super.clone().
  3. A covariant return type removes the need for callers to cast.
  4. The original and copy have equal field values but different identities.

Shallow Copy

A shallow copy duplicates primitive field values but copies object references as references. The original and clone therefore share the same mutable nested objects.

ShallowCopyExample.java
Copy Try Download
class Address {
    String city;

    Address(String city) {
        this.city = city;
    }
}

class Person implements Cloneable {
    String name;
    Address address;

    Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    public Person clone() {
        try {
            return (Person) super.clone();
        } catch (CloneNotSupportedException exception) {
            throw new AssertionError(exception);
        }
    }

    public static void main(String[] args) {
        Person original =
                new Person("Ayan", new Address("Kanpur"));
        Person copy = original.clone();

        copy.address.city = "Lucknow";

        System.out.println(original.address.city);
        System.out.println(copy.address.city);
        System.out.println(original.address == copy.address);
    }
}

Output:

Lucknow
Lucknow
true

Explanation

  1. The clone() method creates a shallow copy, meaning the address reference is copied as-is.
  2. Both the original and copied Person objects share the exact same Address object in memory.
  3. Therefore, changing the city for the copy also changes the city for the original.
The city changed for both objects because both Person instances refer to the same Address object.

Deep Copy

A deep copy creates independent copies of mutable nested objects. Java does not perform this automatically; the class must define the desired copying behavior.

DeepCopyExample.java
Copy Try Download
class Address implements Cloneable {
    String city;

    Address(String city) {
        this.city = city;
    }

    @Override
    public Address clone() {
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException exception) {
            throw new AssertionError(exception);
        }
    }
}

class Person implements Cloneable {
    String name;
    Address address;

    Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    public Person clone() {
        try {
            Person copy = (Person) super.clone();
            copy.address = address.clone();
            return copy;
        } catch (CloneNotSupportedException exception) {
            throw new AssertionError(exception);
        }
    }

    public static void main(String[] args) {
        Person original =
                new Person("Ayan", new Address("Kanpur"));
        Person copy = original.clone();

        copy.address.city = "Lucknow";

        System.out.println(original.address.city);
        System.out.println(copy.address.city);
        System.out.println(original.address == copy.address);
    }
}

Output:

Kanpur
Lucknow
false

Explanation

  1. The clone() method in Person is overridden to first create a shallow copy and then explicitly clone the address field.
  2. The original and cloned Person objects now have independent Address objects.
  3. Changing the city in the cloned object does not affect the original object.

Shallow Copy vs Deep Copy

FeatureShallow CopyDeep Copy
Primitive fieldsCopied by value.Copied by value.
Immutable referencesUsually safe to share.May be shared because their state cannot change.
Mutable nested objectsShared.Copied according to the design.
Default super.clone()Provides this behavior.Requires additional code.
CostUsually lower.Usually higher.
IndependencePartial.Greater, depending on copy depth.

Covariant clone() Return Type

An overriding method may return a subtype of the parent method's return type. Therefore, an Employee clone method can return Employee instead of Object.

CovariantClone.java
Copy Try Download
@Override
public Employee clone() {
    try {
        return (Employee) super.clone();
    } catch (CloneNotSupportedException exception) {
        throw new AssertionError(exception);
    }
}

Cloning and Inheritance

Calling super.clone() creates an object with the same runtime class as the original, even when the call originates in a parent implementation. Subclasses with additional mutable fields must still ensure those fields are copied correctly.

A clone method designed in a non-final class should clearly document how subclasses participate in copying. Copy constructors or factories often make inheritance-related copy behavior easier to control.

Cloning Arrays

Every array type supports a public clone() method. Cloning a one-dimensional primitive array copies its values. Cloning an object array copies its element references, so referenced objects remain shared.

ArrayClone.java
Copy Try Download
import java.util.Arrays;

class ArrayClone {
    public static void main(String[] args) {
        int[] original = {10, 20, 30};
        int[] copy = original.clone();

        copy[0] = 99;

        System.out.println(Arrays.toString(original));
        System.out.println(Arrays.toString(copy));
        System.out.println(original == copy);
    }
}

Output:

[10, 20, 30]
[99, 20, 30]
false

Explanation

  1. Cloning a primitive array creates a completely new array with the primitive values copied over.
  2. Modifying an element in the cloned array does not affect the original array.
  3. The arrays are distinct, so original == copy evaluates to false.

Copy Constructor

A copy constructor accepts another object of the same class and explicitly copies its state. This approach avoids the protected clone API and makes shallow or deep-copy decisions visible.

Customer.java
Copy Try Download
class Address {
    private final String city;

    Address(String city) {
        this.city = city;
    }

    Address(Address source) {
        this.city = source.city;
    }
}

class Customer {
    private final String name;
    private final Address address;

    Customer(String name, Address address) {
        this.name = name;
        this.address = new Address(address);
    }

    Customer(Customer source) {
        this.name = source.name;
        this.address = new Address(source.address);
    }
}
Advantages of copy constructors:
  • No marker interface is required.
  • No checked cloning exception is exposed.
  • Validation and defensive copying are explicit.
  • The copy depth is easier to understand.

Copy Factory Method

A static factory can provide a named copy operation and may return a different implementation when appropriate.

Order.java
Copy Try Download
class Order {
    private final int id;
    private final String status;

    private Order(int id, String status) {
        this.id = id;
        this.status = status;
    }

    static Order copyOf(Order source) {
        return new Order(source.id, source.status);
    }
}

Records and Copying

Records are transparent data carriers. Creating a new record from an existing record's components is usually clearer than implementing Cloneable. Remember that mutable components are still references and may require defensive copies.

RecordCopy.java
Copy Try Download
record Point(int x, int y) { }

class RecordCopy {
    public static void main(String[] args) {
        Point original = new Point(10, 20);
        Point copy = new Point(original.x(), original.y());

        System.out.println(original.equals(copy));
        System.out.println(original == copy);
    }
}

Output:

true
false

Explanation

  1. Rather than implementing Cloneable, records can be cleanly copied by passing their components into a new constructor.
  2. The two Point objects are distinct (different identities), so original == copy is false.
  3. They hold the same values, so equals() evaluates to true.

CloneNotSupportedException

CloneNotSupportedException is a checked exception thrown by Object.clone() when the object's class does not implement Cloneable.

InvalidClone.java
Copy Try Download
class InvalidClone {
    @Override
    public InvalidClone clone()
            throws CloneNotSupportedException {
        return (InvalidClone) super.clone();
    }

    public static void main(String[] args) {
        try {
            new InvalidClone().clone();
        } catch (CloneNotSupportedException exception) {
            System.out.println("Cloning is not supported");
        }
    }
}

Output:

Cloning is not supported

Explanation

  1. The InvalidClone class calls super.clone() but does not implement the Cloneable marker interface.
  2. The JVM checks for the interface and immediately throws a CloneNotSupportedException.
  3. The exception is caught and the error message is printed.

Important Object Cloning Rules

  • Object.clone() is protected.
  • Cloneable is a marker interface with no methods.
  • super.clone() produces a shallow field copy.
  • The clone has a different identity from the original.
  • The clone normally has the same runtime class as the original.
  • Mutable nested objects require explicit copying for deep-copy behavior.
  • Constructors are not normally called by Object.clone().
  • Final fields cannot be reassigned during post-clone adjustment.
  • Arrays support public cloning without implementing Cloneable explicitly in source code.
  • Copy constructors and factories are often preferred for clear APIs.

Best Practices

  • Document whether a copy is shallow, deep, or selectively deep.
  • Use covariant return types for public clone methods.
  • Copy mutable nested state when independence is required.
  • Share immutable objects rather than copying them unnecessarily.
  • Prefer copy constructors or factory methods when they make semantics clearer.
  • Preserve class invariants and validation rules during copying.
  • Write tests that mutate nested fields to verify the intended copy depth.

Tricky Cases

1. Does implementing Cloneable create a public clone() method?

No. The class must expose its own method if callers should clone it directly.

2. Does super.clone() call a constructor?

No. Object cloning allocates and copies the object without running its constructors in the ordinary way.

3. Are String fields safe to share in a shallow copy?

Yes. Strings are immutable, so sharing a String reference does not allow either object to modify the String.

4. Is a cloned collection a deep copy?

Usually not. A typical collection copy creates a new container while retaining references to the same elements.

5. Can a final field be replaced in clone()?

No. A final field cannot be reassigned during the clone method, which complicates deep cloning of mutable final fields.

6. Must clone.equals(original) be true?

Not as a language guarantee. Well-designed value-oriented copies often compare equal initially, but equality is defined by the class.

7. Must clone.getClass() equal original.getClass()?

The conventional Object clone contract expects the clone to have the same runtime class when super.clone() is used.

Common Mistakes

  • Implementing Cloneable but forgetting to expose clone().
  • Assuming super.clone() performs a deep copy.
  • Forgetting to copy mutable nested objects.
  • Calling super.clone() without implementing Cloneable.
  • Exposing Object as the clone return type unnecessarily.
  • Ignoring final mutable fields that cannot be replaced after cloning.
  • Failing to document whether nested state is shared.
  • Using cloning where a copy constructor would be simpler and safer.
Summary:
  • Object cloning creates a distinct object from existing state.
  • Object.clone() is protected and performs a shallow copy.
  • Cloneable is a marker interface with no methods.
  • Mutable nested objects are shared by a shallow copy.
  • Deep copying requires explicit copying of mutable nested state.
  • Arrays provide a public clone() operation.
  • Copy constructors and copy factories often provide clearer semantics.
  • Every copy API should document its copy depth and sharing behavior.

Interview Questions ⭐

Object cloning creates a new object whose fields initially contain values copied from an existing object.

The clone() method declared in java.lang.Object provides Java's built-in cloning mechanism.

The clone() method is declared as a protected method in the Object class.

Cloneable is a marker interface that tells Object.clone() that instances of the class may be cloned.

No. Cloneable is a marker interface and declares no methods.

No. Object.clone() remains protected, so a class normally exposes its own public clone() method.

Object.clone() throws CloneNotSupportedException.

CloneNotSupportedException is a checked exception.

Object.clone() performs a shallow field-by-field copy.

A shallow copy duplicates primitive field values but copies reference fields as references, so mutable nested objects remain shared.

A deep copy creates independent copies of the mutable nested objects that should not be shared with the original.

No. Deep-copy behavior must be implemented explicitly according to the class's object graph and sharing requirements.

A shallow copy shares referenced nested objects, while a deep copy creates independent copies of selected mutable nested objects.

No. Object.clone() creates and copies the object without invoking its constructors through the normal construction process.

Yes. A successful clone is a different object, so clone == original is false.

Yes. A conventional clone created through super.clone() has the same runtime class as the original object.

Not as a language guarantee. It depends on how the class implements equals(), although value-oriented copies often compare equal initially.

It allows an overriding clone() method to return the specific class type instead of Object.

Yes. Immutable objects such as String can generally be shared safely because their state cannot be modified.

The clone method must explicitly create or obtain independent copies of the mutable nested objects and assign them to the cloned object.

No. A final field cannot be reassigned during post-clone adjustment, which makes deep cloning of mutable final fields difficult.

Yes. Every Java array type provides a public clone() method.

For a one-dimensional primitive array, clone() creates a new array containing copied primitive values.

No. It creates a new array but copies the element references, so the referenced objects remain shared.

A copy constructor accepts another object and explicitly copies its state into a newly constructed object.

Copy constructors make validation, copy depth, defensive copying, and the resulting type explicit without relying on Cloneable or CloneNotSupportedException.

A copy factory is a named static method, such as copyOf(), that creates a new object from an existing object.

Usually no. Creating a new collection commonly copies its element references, so mutable elements remain shared unless copied separately.

Yes. A copy API should clearly state whether it performs a shallow, deep, or selectively deep copy and which values remain shared.

A copy constructor or named copy factory is often safer because the intended copy behavior can be implemented and documented explicitly.

Next step: Learn Java Exception Handling

Continue to Java Exception Handling →

Test your understanding with a quick quiz



🚀 Quick Knowledge Check

Topic: Object-cloning | Language: Java

Question 1 of 25
Q1. What is the access level of Object.clone()?
Q2. Can final mutable reference fields complicate deep cloning?
Q3. Can immutable String fields be shared by a shallow clone?
Q4. Does Cloneable declare clone()?
Q5. Where is the clone() method declared?
Q6. What type of copy does super.clone() normally perform?
Q7. What is the best copying guideline?
Q8. What is a copy factory method?
Q9. Can an array be cloned directly?
Q10. Must clone.equals(original) always be true?
Q11. Which is a key advantage of a copy constructor?
Q12. Does copying a mutable collection automatically deep-copy its elements?
Q13. What kind of interface is Cloneable?
Q14. Are constructors normally called during Object cloning?
Q15. What is the output?
class Item implements Cloneable {
    public Item clone() {
        try { return (Item) super.clone(); }
        catch (CloneNotSupportedException e) { throw new AssertionError(e); }
    }
    public static void main(String[] args) {
        Item first = new Item();
        Item second = first.clone();
        System.out.println(first == second);
    }
}
Q16. What is required for a deep copy of a mutable nested object?
Q17. What does cloning a one-dimensional primitive array copy?
Q18. Which statement about implementing Cloneable is correct?
Q19. What is the output of this shallow-copy example?
class Box { int value; Box(int value) { this.value = value; } }
class Item implements Cloneable {
    Box box = new Box(10);
    public Item clone() {
        try { return (Item) super.clone(); }
        catch (CloneNotSupportedException e) { throw new AssertionError(e); }
    }
    public static void main(String[] args) {
        Item first = new Item();
        Item second = first.clone();
        second.box.value = 99;
        System.out.println(first.box.value);
    }
}
Q20. What is a copy constructor?
Q21. What does a shallow copy do with a mutable reference field?
Q22. What happens when super.clone() is called for a class that does not implement Cloneable?
Q23. Which clone() return type is most convenient in an Employee class?
Q24. Is CloneNotSupportedException a checked exception?
Q25. What does cloning an object array copy?

Great job! Continue learning Java step by step.

Discussion

Ask questions, share suggestions, or discuss this lesson.

Please login or create an account to join the discussion and save your learning activity.

Loading comments...

Have you completed this lesson?

Mark this lesson as completed to track your learning progress. To keep track across devices, please login and save your learning progress.

Not completed yet.