All topics
library
intermediate

Immutability 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 state
5
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 everyone
6
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 everywhere
8
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.