Definition
Two copy mechanisms exist, differing in how they handle nested mutable objects.
- Shallow copy copies the object's fields as-is. For primitives, the value is copied (independent). For object references, the reference is copied (both original and copy point to the same nested object). Modifying the nested object through the copy also changes the original.
- Deep copy copies the object and recursively copies all nested objects, creating completely independent copies. Modifying the deep copy has zero effect on the original.
- Three ways to deep copy in Java: copy constructor (recommended: explicit, type-safe),
clone()override (discouraged: Cloneable is a broken marker), serialization (automatic but slow and requires Serializable). - Immutable objects (String, BigDecimal, LocalDate, records) are safe in shallow copies because they cannot be modified.
- Production guideline: design objects to be immutable (records, final fields, unmodifiable collections) to eliminate the need for deep copying.
Shallow copy: you photocopy Rajesh's account file at National Bank of India. The photocopy has the same address card clipped to it (shared reference). If you write a new address on the card, both the original file and the photocopy show the new address. Deep copy: you photocopy the file and make a separate copy of the address card. Now they have independent cards. Changing the copy's address does not affect the original.
Understanding the Concepts
What is shallow copy vs deep copy?
The difference lies in how mutable nested objects are handled.
What does a shallow copy look like?
package com.bank.core;
import java.math.BigDecimal;
public class Account {
private String accountId;
private BigDecimal balance;
private Address address; // mutable nested object
public Account shallowCopy() {
Account copy = new Account();
copy.accountId = this.accountId; // String is immutable: safe
copy.balance = this.balance; // BigDecimal is immutable: safe
copy.address = this.address; // Reference copied: SAME object
return copy;
}
}
Account original = new Account("SAV2001", balance, new Address("42 MG Road", "Mumbai"));
Account shallow = original.shallowCopy();
shallow.getAddress().setCity("Delhi");
System.out.println(original.getAddress().getCity()); // "Delhi": original changed!
// Both point to the SAME Address object.
What does a deep copy look like?
public class Account {
public Account deepCopy() {
Account copy = new Account();
copy.accountId = this.accountId; // immutable: safe
copy.balance = this.balance; // immutable: safe
copy.address = new Address( // NEW Address: independent
this.address.getStreet(),
this.address.getCity()
);
return copy;
}
}
Account deep = original.deepCopy();
deep.getAddress().setCity("Delhi");
System.out.println(original.getAddress().getCity()); // "Mumbai": original unchanged!
What are the three ways to deep copy?
// Way 1: Copy constructor (recommended)
public class Account {
public Account(Account source) {
this.accountId = source.accountId;
this.balance = source.balance;
this.address = new Address(source.address); // Address also has copy constructor
}
}
// Way 2: Cloneable + clone() override (discouraged)
public class Account implements Cloneable {
@Override
public Account clone() {
try {
Account copy = (Account) super.clone(); // shallow copy
copy.address = new Address(this.address); // manually deep copy nested
return copy;
} catch (CloneNotSupportedException e) { throw new RuntimeException(e); }
}
}
// Problems: Cloneable is a marker (no method), super.clone() is shallow, checked exception.
// Way 3: Serialization (automatic but slow)
public static <T extends java.io.Serializable> T deepCopy(T object) {
try {
var bos = new java.io.ByteArrayOutputStream();
new java.io.ObjectOutputStream(bos).writeObject(object);
var bis = new java.io.ByteArrayInputStream(bos.toByteArray());
return (T) new java.io.ObjectInputStream(bis).readObject();
} catch (Exception e) { throw new RuntimeException(e); }
}
// Handles any depth automatically but slow and requires Serializable.
Why are immutable objects safe in shallow copies?
// String, BigDecimal, LocalDate are immutable.
// Shallow copy shares the reference but the object cannot be modified.
Account shallow = original.shallowCopy();
// shallow.accountId and original.accountId point to same String "SAV2001"
// But String is immutable: nobody can change it. Sharing is safe.
// Only MUTABLE nested objects (Address, List<Transaction>) need deep copying.
How do shallow and deep copy compare?
| Aspect | Shallow Copy | Deep Copy |
|--------|-------------|----------|
| Primitives | Independent (value copied) | Independent |
| Immutable references | Safe (cannot be modified) | Safe |
| Mutable references | Shared (dangerous) | Independent (safe) |
| Performance | Fast | Slower (creates new objects) |
| Use case | When all nested objects are immutable | When nested objects are mutable |
What is the best approach in production?
Design objects to be immutable. Records, final fields, and unmodifiable collections eliminate the need for deep copying entirely.
// Immutable design: no deep copy needed
public record AccountSummary(
String accountId,
BigDecimal balance,
Address address // Address should also be a record (immutable)
) {}
// Sharing references is safe because nothing can be modified.
What should a senior engineer know?
- Effective Java Item 11: override clone judiciously. Prefer copy constructors over Cloneable.
- In JPA, deep copy matters for detached entities: modifying a detached entity's nested collection can cause unexpected merge behavior.
- Apache Commons Lang provides SerializationUtils.clone() for serialization-based deep copy.
Visual Overview
SHALLOW COPY
┌──────────────┐ ┌──────────────┐
│ original │ │ shallow copy │
│ accountId ────┼──┐ │ accountId ────┼──┐
│ balance ──────┼──┤ │ balance ──────┼──┤
│ address ──────┼──┤ │ address ──────┼──┘
└──────────────┘ │ └──────────────┘
│ │
└─────┬────┘
▼
┌──────────────┐
│ Address │ SHARED
│ "42 MG Road" │
│ "Mumbai" │
└──────────────┘
DEEP COPY
┌──────────────┐ ┌──────────────┐
│ original │ │ deep copy │
│ address ──────┼──┐ │ address ──────┼──┐
└──────────────┘ │ └──────────────┘ │
▼ ▼
┌──────────┐ ┌──────────┐
│ Address │ │ Address │
│ "Mumbai" │ │ "Mumbai" │
└──────────┘ └──────────┘
INDEPENDENT INDEPENDENT
Key Takeaway
Shallow copy copies field values as-is: primitives are independent, but object references are shared. If you modify a mutable nested object through the copy, the original is also affected. Deep copy recursively copies all nested objects, creating completely independent copies. Modifying the deep copy has zero effect on the original. Immutable objects (String, BigDecimal, records) are safe in shallow copies because they cannot be modified.
Three ways to deep copy in Java: copy constructor (recommended: explicit and type-safe), clone() override (discouraged: Cloneable is a broken marker interface), and serialization (automatic but slow). In production, prefer immutable design: records with final fields and unmodifiable collections eliminate the need for deep copying entirely.
Remember one sentence: shallow copy shares mutable nested references (dangerous), deep copy creates independent copies (safe), and the best solution is to design objects as immutable so deep copying is never needed.
---