Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Immutability & Defensive Copies

โญโญ intermediateโณ 14 min read๐Ÿ“ Lesson 65 of 180

Building truly immutable classes, and the two directions mutability leaks through final fields and return values.

Immutability and defensive copying

An immutable class: final fields, no setters, no leaked mutable state. Immutable objects are thread-safe by construction, safe map keys, and easy to reason about.

public final class Range {
    private final int lo, hi;
    public Range(int lo, int hi) {
        if (lo > hi) throw new IllegalArgumentException("lo > hi");
        this.lo = lo; this.hi = hi;
    }
    public int lo() { return lo; }
    public int hi() { return hi; }
}

Mutability leaks in two directions:

1. Mutable field leaked โ€” the caller can change your state without going through your methods:

public final class Team {
    private final List<String> members;
    Team(List<String> members) { this.members = members; }
    public List<String> members() { return members; }  // LEAK
}
// caller: team.members().clear();  โ€” your "final" field is now empty

Fix: return an unmodifiable view (or a copy) and copy on the way in:

Team(List<String> members) { this.members = List.copyOf(members); }
public List<String> members() { return Collections.unmodifiableList(members); }

2. Mutable field stored โ€” you keep a reference to the caller's list; they mutate it later and your object changes underneath you.

record solves the field-leak but still shallow-copies arrays and lists โ€” a record holding a mutable List is only as immutable as that list allows.

โšก Now practice

Ready to Code
Contract LabBuild value classes with correct equals/hashCode, seal leaky APIs, and defeat the Integer-cache trap.
3 challenges ยท ยท ~35 min