skip to content

What is the difference between a final reference and an immutable object? Can a final variable still 'change'?

level: middleimportance: must knowfreq 65%

answer

  1. final = variable; immutable = object
  2. orthogonal concepts
  3. final List still allows add()
  4. truly fixed = final + immutable
  5. records aren't deeply immutable by default

basics

~20 s

final stops you from pointing the variable at a different object. It does not stop the object itself from changing. So a final List can still have items added to it; you just cannot replace the whole list with a new one.

solid answer

~40 s

These are two independent ideas. final applies to the variable (the reference/binding): once set, you cannot make it point to another object. Immutability applies to the object: once created, its observable state never changes (e.g. String, Integer, or a record with only final fields and no mutators). A variable can be final while its object is mutable — final List<String> still allows list.add(...). Conversely an object can be immutable while held by a non-final variable that you freely repoint. So yes, the contents reachable through a final variable can absolutely change. To get a value that truly cannot change, you need both: a final variable holding an immutable object, ideally with no escaping mutable internals. This distinction matters for thread-safety, defensive copying, and API design.

code

java · 6 lines
java
final StringBuilder sb = new StringBuilder("a");
sb.append("b");   // OK: object mutates -> "ab"
// sb = new StringBuilder(); // ERROR: cannot repoint final var

final String s = "hello";
// s is final AND String is immutable -> nothing about s can change

go deeper

for a junior

Recognizes that a final List can still have elements added; final is about the variable, not the contents.

for a middle

Cleanly separates final (binding) from immutability (object state) and gives the four combinations.

for a senior

Explains how to build immutable objects (final fields, defensive copies, no escaping state) and the thread-safety implications.

for a principal

Designs APIs and domain models around immutability, reasons about deep vs. shallow immutability, escaping mutable state, and the JMM safe-publication guarantees of final fields.

## Two separate concepts Beginners often merge two distinct ideas. Keep them apart: - **`final` variable (the reference / binding):** the named slot can be assigned only once. After that you cannot make it refer to a *different* object. - **Immutable object (the value):** the object's observable state never changes after construction. Examples: `String`, the boxed types (`Integer`, `Long`), `LocalDate`, and well-designed `record`s whose fields are `final` and which expose no mutators. `final` says nothing about whether the object can change; immutability says nothing about whether the variable can be repointed. They are orthogonal. ## A final variable's contents CAN change ```java final List<String> names = new ArrayList<>(); names.add("Ada"); // OK — the object mutates names.add("Linus"); // OK // names = List.of(); // ERROR — cannot repoint the final variable ``` The variable `names` is frozen to one `ArrayList`, but that `ArrayList` is mutable, so the data you see through `names` changes over time. ## The four combinations | Variable | Object | Effect | |---|---|---| | non-final | mutable | repoint and mutate — fully changeable | | non-final | immutable | object never changes, but you can repoint to another | | **final** | **mutable** | cannot repoint, but object state can change | | **final** | **immutable** | truly fixed value — cannot repoint, cannot mutate | Only the **final + immutable** row gives you a value that cannot change in any way. ## How to build immutability To make an object immutable: make all fields `final`, provide no setters or other mutators, do not let internal mutable state escape (return copies or unmodifiable views), and prevent subclassing if needed (e.g. `final class`). `record`s help: their components are implicitly `final`, but a record can still hold a mutable field (e.g. a `List`), so a record is **not automatically deeply immutable** — you must defensively copy. ```java public record Point(int x, int y) {} // immutable, deeply public record Team(String name, List<String> members) { public Team { // compact constructor members = List.copyOf(members); // defensive copy → safe } } ``` ## Why it matters - **Thread-safety:** truly immutable objects can be shared across threads without synchronization. A `final` reference to a *mutable* object is not thread-safe by itself. - **Reasoning:** immutable values are easier to reason about and cache. - **APIs:** returning a `final` field that is a mutable collection still lets callers mutate your internals unless you copy or wrap it.

  • Does declaring a record make it immutable?
    Record components are implicitly final, so the references cannot be repointed. But if a component is a mutable type like List, the record is not deeply immutable; defensively copy in the compact constructor and on access to truly protect it.
  • Why does immutability help with thread-safety?
    An object whose state never changes can be read by many threads at once with no risk of seeing a partial or inconsistent update, so no synchronization is needed. A final reference to a mutable object gives no such guarantee.

final is a locked picture frame bolted to the wall (you cannot swap the frame). Immutability is whether the picture inside is printed permanently or is a whiteboard. A bolted frame around a whiteboard still lets the drawing change.

saying these in an interview costs you the question

  • Saying final makes the object immutable.
  • Assuming a record is automatically deeply immutable.
  • Believing a final collection cannot be modified at all.
  • Conflating 'cannot repoint the variable' with 'state cannot change'.

context