Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Tools for values that don't change

final, immutable Strings, final fields with no setters, records, defensive copies, List.of and unmodifiable views.

Pick the tool by what must not change.

  • A local variable or a constant → final (and static final for a class-wide constant). It fixes the variable, not the object it refers to.
  • A whole class of values (money, a date range, a point) → a record, or a class with private final fields and no setters.
  • A field that holds a collection or an array → copy it on the way in and on the way out (List.copyOf, clone), or the caller can still change your object's insides.
  • A collection you hand out → List.of/List.copyOf for a true snapshot; Collections.unmodifiableList only for a read-only VIEW that still shows later changes.
  • Text → nothing to do: String is already immutable. Keep the returned string.

Variables that can't be reassigned

A variable declared final, whose value can't be changed after it is first set.

Example

static final int MAX_SEATS = 120;

A static final value fixed at compile time or class load.

A field that must be assigned exactly once, by the end of every constructor, and never again.

Assigned once, in the constructor, never again.

A local variable that is never reassigned after its first value, so a lambda may use it.

A local that's never reassigned, which is what a lambda may capture.

Objects that can't change

Formal. A String object never changes after it is created. Every method that seems to modify text, such as toUpperCase, strip, replace or substring, returns a new String and leaves the original untouched. A variable can be reassigned to refer to the new string; the old object is unchanged. == compares references, so text must be compared with equals (or equalsIgnoreCase).

In plain English. A string is printed on paper, not written on a whiteboard. Asking for an upper-case version prints a fresh sheet. If you don't keep the fresh sheet, by assigning it to a variable, nothing has changed.

Example. After String name = " ada "; name.strip(); the variable still holds " ada ". name = name.strip(); keeps the result. "ab-cd".replace("-", "") returns "abcd" and replaces every match.

Why this rule exists. Because no one can change a string behind your back, strings are safe to share and to cache, and to use as lookup keys.

Every String method returns a new string.

Can't be changed after it is created.

An object whose state never changes after construction.

Why a setter for every field gives encapsulation away.

A copy of a mutable object made on the way in or out of a class, so outside code can't change its internals.

Example

this.items = List.copyOf(items);

Copy a mutable argument or field so callers can't reach inside.

Records with compact constructors, copies of mutable components, and 'with' methods.

An object identified only by its data, so two with the same data are interchangeable.

An object defined by its value, not its identity.

Collections that can't change

List.of, copyOf, and unmodifiable views.

A collection whose elements can be read but not added, removed or replaced.

Example

List.of(1, 2).add(3) throws.

add and remove throw UnsupportedOperationException.

A list whose elements can be read but never added, removed or replaced.

A list that rejects changes; its elements may still be mutable objects.