library
intermediateImmutability in Java
Design immutable classes correctly and understand their benefits for thread safety and defensive programming.
An immutable object cannot change after construction. That single property removes a whole class of bug.
Immutable object = a printed book. Once published, its content can't change. Everyone reads the same thing. Mutable object = a wiki page. Anyone can edit it while you're reading, and you might see inconsistent state.
Key Concepts
1
public final class Money {
private final long amount;
private final List<String> tags;2
public Money(long amount, List<String> tags) {
this.amount = amount;
this.tags = List.copyOf(tags); // copy on the way in
}3
public List<String> tags() { return tags; } // already unmodifiable
public Money plus(long more) { return new Money(amount + more, tags); }
}4
The five rules.
1. make the class final, so nobody adds mutable behaviour
2. make every field private and final
3. provide no setters
4. copy mutable arguments in the constructor
5. never hand out a reference to internal mutable state5
Rules 4 and 5 are the ones people skip.
this.tags = tags; // the caller still holds the list
public List<String> tags() { return tags; } // now so does everyone6
Either line defeats the other four. The object is "immutable" and its contents change anyway.
7
What it buys.
thread safety share it freely, no synchronisation
safe as a map key the hashCode cannot drift
safe to cache nothing changes under you
no temporal coupling -- it means the same thing everywhere8
The threading guarantee is a real JVM rule. Once a constructor completes, every thread sees the correct
values of that object's final fields without any synchronisation. That is what makes immutable objects safe
to publish by a plain assignment.
9
The JDK's own: String, every wrapper, LocalDate, BigDecimal, List.of(...).
StringLocalDateBigDecimalList.of(...)
10
Records give you most of it:
record Money(long amount, List<String> tags) { }11
Final class, final fields, no setters, equals and hashCode for free. But a record does not copy, so
that tags list is still mutable by whoever passed it. Add a compact constructor:
equalshashCodetags
12
record Money(long amount, List<String> tags) {
Money { tags = List.copyOf(tags); }
}13
The cost is allocation. Every change makes an object, so a hot loop mutating state wants a builder or a
mutable type, and the immutable version at the boundaries.
14
What the interviewer is probing.1. "What are the rules for an immutable class?" Probing: completeness. Stalls: "Make the
fields final." Moves up: final class, private final fields, no setters, copy mutable arguments in,
and never hand out a reference to internal mutable state.
15
2. "Which two rules do people usually skip?" Probing: the defensive copies. Stalls: "None."
Moves up: copying on the way in and on the way out — either omission lets a caller mutate the
object through the reference they still hold.
16
3. "Is a record immutable?" Probing: the limit. Stalls: "Yes." Moves up: its components
are final, but a component holding a List is not; a compact constructor with List.copyOf fixes it.
17
4. "What does immutability give you in concurrent code?" Probing: the publication guarantee.
Stalls: "Thread safety." Moves up: final fields are visible to every thread once the constructor
completes, so the object can be shared by a plain assignment with no synchronisation.