Question 16 · Section 13

Why should an immutable class be final?

An immutable class must be final so that no one can subclass it and add mutable fields or methods.

Language versions: English Russian Ukrainian

Junior Level

An immutable class must be final so that no one can subclass it and add mutable fields or methods.

public final class Point {  // final — cannot be inherited
    private final int x;
    private final int y;
    // ...
}

If you remove final, someone can write:

public class MutablePoint extends Point {
    private int z; // new field
    public void setZ(int z) { this.z = z; } // mutation!
}

Middle Level

Protection against behavior substitution

Without final, a subclass can:

  1. Add mutable state — new fields with setters
  2. Override getters — return different data
  3. Override equals/hashCode — break operation in HashMap

Attack scenario

public void process(ImmutablePoint point) {
    validate(point);         // check coordinates
    // here a subclass can change the data
    saveToDatabase(point);   // save already changed data
}

Examples from JDK

public final class String { ... }
public final class Integer { ... }
public final class BigDecimal { ... }
public final class LocalDate { ... }

What if you need inheritance?

Use Sealed classes (Java 17+) to control heirs, or composition.

When final can be a problem

  • Mocking in tests: Mockito cannot mock final classes without bytebuddy-agent
  • Proxy frameworks: some require inheritance (Spring AOP via CGLIB)
  • Solution: sealed classes or Mockito inline-mock-maker

Senior Level

JMM and JIT guarantees

final class + final fields creates an “impenetrable” shell:

  • JMM: final fields ensure safe publication with memory barrier
  • JIT: compiler can aggressively inline methods and cache values, knowing the class won’t be overridden

Problems without final

  1. HashMap corruption — a subclass can override hashCode(), returning different values
  2. Security vulnerabilities — TOCTOU attacks through getter substitution
  3. Thread-safety loss — subclass accesses non-final fields without synchronization

Summary for Senior

  • final class prevents logic changes through method overriding
  • Without final, you cannot guarantee thread-safety or hash code stability
  • final class — a signal to other developers and the compiler about an unchanging contract
  • Use sealed classes for controlled inheritance (Java 17+)

Interview Cheat Sheet

Must know:

  • final prohibits inheritance — subclass cannot add mutability
  • Without final, a subclass can: add mutable fields, override getters, break equals/hashCode
  • HashMap corruption — subclass overrides hashCode(), object gets lost
  • Security vulnerabilities — TOCTOU attacks through getter substitution
  • JMM: final class + final fields = impenetrable shell with safe publication
  • When final interferes: mocking in tests, proxy frameworks (CGLIB); solution: sealed classes or inline-mock-maker

Frequent follow-up questions:

  • What happens without final? — Subclass adds mutable fields, breaks hashCode and thread-safety
  • Mockito can’t mock final classes? — Need inline-mock-maker or bytebuddy-agent
  • Sealed classes as an alternative? — Yes, Java 17+: limit the circle of heirs
  • Does JIT benefit from final? — Yes: aggressive inlining, caching, since class won’t be overridden

Red flags (do NOT say):

  • “Immutability works without final” — a subclass can break everything
  • “Final is just for style” — it’s a guarantee of security and JIT optimizations
  • “Mockito doesn’t work with final” — it works with inline-mock-maker
  • “Sealed classes = final” — sealed allows inheritance from specific classes

Related topics:

  • [[7. What is the final keyword and how does it help create immutable classes]]
  • [[15. Can you inherit from an immutable class]]
  • [[17. What happens if you override getter in subclass of immutable class]]
  • [[1. What is an immutable object]]