Java Core 7 min read

Java Object Cloning: Shallow vs Deep — A Visual Guide

When you clone an object in Java, do you get a truly independent copy — or just a ghost that still shares memory with the original? The answer depends on whether you're doing shallow or deep cloning. Let's visualise exactly what happens in memory.

A
Jun 7, 2026 · 7 min read

What is Object Cloning?

Java's Object.clone() creates a copy of an object. But "copy" means very different things depending on how you implement it. The Cloneable interface is a marker — it tells the JVM that this class permits cloning. Without it, clone() throws CloneNotSupportedException.

There are two types of cloning and the difference is critical when your object contains reference fields (fields that point to other objects):

📋
Shallow Cloning

Creates a new object and copies primitive fields by value. Object reference fields are copied by reference — both original and clone point to the same nested objects in memory.

super.clone() — default behaviour
🧬
Deep Cloning

Creates a new object and also clones every referenced object recursively. Original and clone are completely independent — changes to one never affect the other.

override clone() + clone nested objects

Interactive Memory Visualizer

Step through each phase and watch how heap memory is allocated. See which references are shared (shallow) vs independent (deep).

Step 1/4
🏗️
Java Heap Memory
ShallowCopyInJava.java

      

Shallow Cloning

Shallow cloning is the default behaviour provided by Java. Calling super.clone() copies all fields bit-by-bit. For primitives (int, long, etc.) and immutable objects (String), this is perfectly safe — a change in the clone doesn't affect the original.

The problem arises with mutable reference fields like Department dept. The clone gets the same pointer to the same Department object on the heap. Mutate it through the clone, and the original sees the change too.

Watch out: Changing e2.dept.designation also changes e1.dept.designation because both e1.dept and e2.dept are the same object in memory.

Employee.java (Shallow)
class Employee implements Cloneable {
    int id;
    String name;
    String bloodGroup;
    Department dept; // ← reference field — this is the problem

    // Default clone() — shallow copy
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // copies dept reference, not the object
    }
}

public class ShallowCopyInJava {
    public static void main(String[] args) {
        Department dept1 = new Department("A1", "Cloud");
        Employee e1 = new Employee(7012, "Manu", "AB+", dept1);
        Employee e2 = null;

        try {
            e2 = (Employee) e1.clone(); // shallow copy
        } catch (CloneNotSupportedException e) { e.printStackTrace(); }

        System.out.println(e1.dept.designation); // "Cloud"
        e2.dept.designation = "Testing";             // change via clone
        System.out.println(e1.dept.designation); // "Testing" ← original changed!
    }
}

Deep Cloning

Deep cloning requires you to override clone() and manually clone every reference field. The clone gets its own independent Department object — same data, different memory address. From this point on, the two objects are completely independent.

For deeply nested objects you may need to clone recursively: if Department itself has reference fields, Department.clone() must also deep-clone those.

Safe: Changing e2.dept.designation now only affects e2. e1.dept is a completely separate object at a different heap address.

Employee.java (Deep)
class Department implements Cloneable {
    String floorId;
    String designation;
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // safe here — only String fields (immutable)
    }
}

class Employee implements Cloneable {
    int id;
    String name;
    String bloodGroup;
    Department dept;

    // Deep clone — also clone the nested Department
    protected Object clone() throws CloneNotSupportedException {
        Employee emp = (Employee) super.clone();  // step 1: shallow copy all fields
        emp.dept = (Department) dept.clone();          // step 2: replace dept with its own copy
        return emp;
    }
}

Shallow vs Deep — Comparison

Shallow Cloning Deep Cloning
Cloned & source objects are not entirely separate — they share nested objects. Cloned & source objects are completely independent — no shared references.
Modifications to reference fields in the clone affect the original. Modifications to reference fields in the clone do not affect the original.
Default behaviour — just call super.clone(). No extra code needed. Must override clone() and explicitly clone every reference field.
Good choice when all fields are primitives or immutable types (int, String, etc.). Necessary when fields hold mutable object references.
Relatively fast — single allocation, no recursion. Relatively slower — allocates and copies every nested object.

When to use which?

📋 Use Shallow when…
  • All fields are primitives (int, double)
  • Reference fields are immutable (String, LocalDate)
  • You intentionally want shared state (flyweight pattern)
  • Performance-critical with very large object graphs
🧬 Use Deep when…
  • Object contains mutable reference fields
  • The clone must be a completely independent copy
  • You're implementing undo/redo or snapshot features
  • Multithreaded code where isolated state is required
Modern Alternative: Copy Constructors & Serialization

Many Java developers today prefer copy constructors (new Employee(other)) or serialization-based cloning over Cloneable. These give more control and avoid the pitfalls of Java's flawed clone mechanism. With Java Records, immutability makes cloning largely unnecessary.

Java Cloning OOP Memory Design Patterns