← Back to Java

Immutable Class in Java

JavaMidjava

The Question

What is an immutable class and how do you create one?

What a Strong Answer Covers

  • class final
  • fields private final
  • no setters
  • constructor initialization
  • deep copy mutable parameters
  • return copies in getters
  • thread safe without synchronization
  • String as example

Senior-Level Answer

An immutable class is a class whose instances cannot be modified after construction. Once created, an immutable object's state is fixed for its lifetime. java.lang.String, Integer, and the other wrapper types are the canonical examples.

Immutable objects offer significant advantages: they are inherently thread-safe (no synchronization needed since state cannot change), safe to share and cache, easy to reason about, and make excellent keys in HashMap or elements in HashSet because their hashCode never changes.

To create an immutable class, follow these five rules:

1. Declare the class final (or use private constructors with static factory methods) to prevent subclassing that could override methods and introduce mutability. 2. Make all fields private and final so they are set once in the constructor and cannot be reassigned. 3. Do not provide any setter methods or other mutating methods. 4. Perform defensive copies of mutable objects passed into the constructor. If a constructor accepts a Date or a List, copy it immediately — otherwise a caller could mutate the external reference after construction, changing the object's state. 5. Perform defensive copies of mutable objects returned from getters. If a field is a mutable collection, return a copy or an unmodifiable view — otherwise callers can mutate the internal state through the reference.

Record classes introduced in Java 16 provide a concise immutable class syntax: the compiler auto-generates a canonical constructor, private final fields, accessors (not getters in JavaBeans style), equals(), hashCode(), and toString(). However, records don't automatically deep-copy mutable fields — you still need a compact constructor to perform defensive copies if fields contain mutable types.

A common mistake is thinking that declaring a field final makes the object it references immutable. final only prevents reassigning the reference; the referenced object itself can still be mutated if it is a mutable type.

What Separates a 2/3 from a 3/3

2/3 — Passing but Incomplete

Lists the core rules (final class, private final fields, no setters, defensive copies) and explains why immutability implies thread safety.

3/3 — Strong Answer

Distinguishes field-level final (reference immutable) from object-level immutability, explains both incoming and outgoing defensive copies, mentions Java Records as the modern idiom, and gives HashMap key safety as a practical benefit.

Common Mistakes

  • Thinking final fields make an object immutable — final prevents reference reassignment but not mutation of the referenced mutable object.
  • Forgetting defensive copies on getter return values — returning a direct reference to an internal mutable collection breaks immutability.
  • Omitting the final class modifier — a subclass could add setters and break the immutability contract for callers using a reference to the parent type.

Follow-Up Questions

  • String is declared final in Java. What would happen if it weren't? — A subclass could override methods like charAt() or length(), allowing callers to receive a mutable 'String' where an immutable one is expected, breaking security assumptions (e.g., in class loading and file paths).
  • How do Java Records differ from manually written immutable classes? — Records auto-generate constructor, accessors, equals/hashCode/toString, and are implicitly final. But they don't auto-copy mutable fields — a compact constructor is needed for defensive copies of List/Date/array fields.
  • Can an immutable class have methods that appear to change state, like withName(String name)? — Yes — these are 'wither' methods that return a new instance with the updated value rather than mutating the current one, following the same pattern as String.substring() or BigDecimal.add().

Related Questions

  • JVM vs JRE vs JDK
  • Java Platform Independence
  • How JVM Works
  • Main Features of Java
  • public static void main

Can You Explain This Cold?

Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.

Get Graded — Free Assessment
GrindQuestionsAITechnical interview assessment
TermsPrivacyAbout