What is the difference between a final reference and an immutable object? Can a final variable still 'change'?
answer
- final = variable; immutable = object
- orthogonal concepts
- final List still allows add()
- truly fixed = final + immutable
- records aren't deeply immutable by default
basics
~20 sfinal 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 sThese 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 linesfinal 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 changego deeper
Recognizes that a final List can still have elements added; final is about the variable, not the contents.
Cleanly separates final (binding) from immutability (object state) and gives the four combinations.
Explains how to build immutable objects (final fields, defensive copies, no escaping state) and the thread-safety implications.
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'.