Skip to main content

Week 1 Day 4 — final and Immutability

Goal

Today I want final and immutability as two different ideas that interviews often mash together.

Main questions:

  1. What does final mean on a variable, method, and class?
  2. What does immutable mean?
  3. How do I make a class immutable?
  4. Why is String immutable?
  5. Why do Spring beans still need care even with final fields?

1. What final actually does

PlaceMeaning
Local / parameter / fieldThe binding cannot change after assignment.
MethodSubclasses cannot override it.
ClassNobody can extend it.
final int n = 3;
// n = 4; // compile error

final List<String> names = new ArrayList<>();
names.add("Ada"); // allowed: the list object mutates
// names = List.of("Bob"); // compile error: new binding

final on a reference is a constant pointer, not a frozen object.

Memory sentence:

final stops reassignment. It does not stop mutation of the object.


2. Immutability

An immutable object cannot change after construction, through any reference.

That is stronger than final fields:

  • fields are private final
  • no setters
  • no methods that mutate
  • mutable inputs are copied in
  • mutable internals are copied out (or never exposed)
  • the class is final (or sealed) so a subclass cannot add mutation
public final class Money {
private final String currency;
private final long cents;

public Money(String currency, long cents) {
this.currency = currency;
this.cents = cents;
}

public Money plus(Money other) {
if (!currency.equals(other.currency)) {
throw new IllegalArgumentException("currency");
}
return new Money(currency, cents + other.cents);
}
}

plus returns a new object. The original is unchanged. Several threads can share Money without locks.


3. Defensive copies

final on a List field is not enough.

public final class Team {
private final List<String> members;

public Team(List<String> members) {
this.members = members; // leak: caller can still mutate
}

public List<String> members() {
return members; // leak: caller can still mutate
}
}

Fix:

this.members = List.copyOf(members); // copy in, unmodifiable
return members; // safe if members is unmodifiable
// or return List.copyOf(members);

A record with a List component has the same leak unless I copy in a compact constructor. Records are shallowly immutable: the fields cannot be reassigned, nested objects can still change.


4. Why String is immutable

String is a final class with private state and no mutators. Methods like toUpperCase return new strings.

Why the language designers wanted that:

  • several references can share one String safely
  • it is a safe HashMap key: the hash cannot change
  • the string pool interned literals are shareable
  • no locking is needed to read a string
String a = "ada";
String b = a.toUpperCase();
// a is still "ada"

Memory sentence:

Immutable values are shareable. Mutable values need a clear owner.


5. Spring connection

Constructor injection with private final collaborators is the default I want:

@Service
public class OrderService {
private final OrderRepository orders;

public OrderService(OrderRepository orders) {
this.orders = orders;
}
}

The field binding is immutable. The OrderService instance is still a shared singleton. If OrderRepository is a Spring proxy with state, or if I add a mutable List<Order> field, I am back to shared mutation.

Judgment:

  • Collaborators: final, injected, no setters.
  • Request data: method parameters, never singleton fields.
  • Domain values (Money, IDs, commands): prefer immutable types.
  • Entities: mutable by JPA’s design. Do not treat them as value objects.

6. Common traps

Trap 1:final means immutable.”
Only the binding is fixed.

Trap 2: Immutable class that returns its internal Date or ArrayList.
Callers mutate through the leaked reference.

Trap 3: Using StringBuilder as if it were a String.
StringBuilder is a mutable buffer. It is the right tool for building text in a loop, not for sharing.

Trap 4: Making a Spring @Service “immutable” and then storing request state in a field.
The type looks clean. The singleton still shares that field across requests.


Practice Questions and Answers

Question 1

What does final mean on a field of type List?

Answer:

I cannot point the field at a different list after assignment. I can still add or remove elements if the list implementation is mutable.


Question 2

How do I make a class immutable?

Answer:

Make the class final, fields private final, no setters, set all state in the constructor, copy mutable inputs, never leak mutable internals. Methods that would “change” state return a new instance.


Question 3

Why are immutable objects useful as map keys?

Answer:

A HashMap places the key in a bucket from hashCode, then finds it with equals. If the key mutates, the hash no longer matches the bucket, and the entry is lost. Immutable keys cannot move.


Question 4

Is a record always immutable?

Answer:

The record’s component bindings cannot be reassigned. If a component is a mutable type (ArrayList, array, bean), the record is only shallowly immutable. Copy in the constructor if I need a real value object.


Question 5

Why do we still use final on Spring service fields?

Answer:

It documents that collaborators are assigned once, enables constructor injection, and prevents accidental reassignment. It does not make the service thread-safe by itself. Thread-safety still depends on not storing request state and on what the collaborators do.


Memory sentences

final stops reassignment; immutability also stops nested mutation.

Share values. Give mutable objects a single owner.

private final collaborators are the Spring default, not a thread-safety proof.

Next: Week 1 Day 5 — Records as Value Objects