Skip to content

24 · 32 · 5 min

Objects that never change

An immutable object is one whose state cannot change after construction. That single restriction removes a surprising number of problems.

Building one

java
public final class Money {
    private final long paise;
    private final Currency currency;

    public Money(long paise, Currency currency) {
        this.paise = paise;
        this.currency = Objects.requireNonNull(currency);
    }

    public Money plus(Money other) {
        if (!currency.equals(other.currency))
            throw new IllegalArgumentException("currency mismatch");
        return new Money(paise + other.paise, currency);   // a new object
    }
}

Four things make it hold: the class is final so nobody subclasses it into mutability, the fields are final and private, no method modifies state, and any mutable field would need defensive copying on the way in and out.

What it buys

Thread safety for free. A value that cannot change cannot be raced. No locks, no synchronisation, no memory visibility questions.

Safe sharing. Return it, cache it, pass it anywhere, with no defensive copy, because nobody can damage it.

Safe as a hash key. The hash cannot drift, which is exactly the failure from the collections page.

Easier reasoning. A value assigned once is a value you can trust for the rest of the method, which removes a whole category of "who changed this".

What it costs

A new object per change. For a loop appending to a string, that is quadratic behaviour, which is why StringBuilder exists alongside immutable String.

java
String s = "";
for (int i = 0; i < 100_000; i++) s += i;     // copies the whole string each time

Allocation is cheap and generational collectors handle short lived objects well, so the cost is usually smaller than expected, and the mitigation when it is not is a mutable builder used locally and frozen at the end.

The practical rule

Default to immutable, and make something mutable when there is a reason.

Make immutableKeep mutable
Values: money, dates, coordinatesLarge buffers built incrementally
ConfigurationLong lived entities with identity
Anything shared across threadsPerformance critical inner loops
Anything used as a key

In other languages

Python's tuples, strings and frozensets are immutable; @dataclass(frozen=True) makes your own. Kotlin distinguishes val from var and listOf from mutableListOf. Rust makes immutability the default and requires mut to opt out, which is the strongest statement any mainstream language makes about it.