How to create an immutable class in Java?
To create an immutable class, follow these steps:
Basic Level
To create an immutable class, follow these steps:
Step 1: Declare the class as final
public final class Person {
Step 2: Make all fields private final
private final String name;
private final int age;
Step 3: Don’t create setters
Only getters (methods for reading):
public String getName() { return name; }
public int getAge() { return age; }
Step 4: Initialize fields in the constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
Complete example
public final class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
When NOT to create an immutable class
- JPA entities — usually mutable (ORM requires setters, no-arg constructor)
- Simple scripts/prototypes — development speed is more important
- DTOs for serialization — often more convenient as mutable with setters
Intermediate Level
Working with mutable fields
If the class contains collections or other mutable objects, you need to make defensive copies:
public final class ImmutableReport {
private final String title;
private final List<String> items;
public ImmutableReport(String title, List<String> items) {
this.title = title;
this.items = (items == null) ? List.of() : List.copyOf(items);
}
public String getTitle() { return title; }
public List<String> getItems() {
return items; // List.copyOf already returned an immutable list
}
}
Implementation via Record (Java 14+)
public record ImmutableReport(String title, List<String> items) {
public ImmutableReport {
items = List.copyOf(items); // compact constructor
}
}
A record automatically:
- Makes the class
final - Makes fields
private final - Generates the constructor,
equals(),hashCode(),toString()
Joshua Bloch’s Five Rules
- Don’t provide mutator methods
- Ensure the class cannot be extended (
final) - Make all fields
final - Make all fields
private - Ensure exclusive access to mutable components
Advanced Level
“this” leak danger (Constructor Escape)
public ImmutableClass() {
ExternalService.register(this); // ERROR!
}
If another thread accesses the object before the constructor completes, it will see final fields in their default state.
Deep Copy
If the list contains mutable objects, List.copyOf is insufficient:
public record Order(Long id, List<Item> items) {
public Order {
items = items.stream()
.map(original -> new Item(original)) // Instead of clone() (which is considered broken — Effective Java Item 13): copy constructor — more reliable
.toList();
}
}
Reflection attack
Starting with Java 9 (Project Jigsaw) and especially Java 16+ (JEP 396), reflection access to java.base fields is blocked by default.
Summary for Advanced
- Regular
finalis not enough for mutable reference types - Always use
List.copyOf()orCollections.unmodifiableList() - Remember Constructor Escape
- For simple DTOs, choose Records
- Deep immutability requires copying the entire object hierarchy
Interview Cheat Sheet
Must know:
- 4 steps: final class, private final fields, no setters, initialize in constructor
- Joshua Bloch’s five rules: no mutators, no extension, final fields, private fields, protect mutable components
- For mutable fields —
List.copyOf()in constructor and return immutable wrapper from getter - Records (Java 14+) automatically make the class final, fields private final, generate equals/hashCode/toString
- Constructor Escape — do not expose
thisduring construction - Deep Copy: if the list contains mutable objects, each element must be cloned
- When NOT to create: JPA entities, simple prototypes, DTOs for serialization
Frequent follow-up questions:
- What to do with collections in the constructor? —
List.copyOf(input)creates an independent copy - Records vs regular class? — Records: minimal code, but no inheritance; regular class: full control
- What is Constructor Escape? — Publishing
thisbefore constructor completes = another thread sees null fields - Is List.copyOf enough for deep immutability? — No, if the list elements are mutable
Red flags (DO NOT say):
- “Just final fields — that’s it” — without defensive copy, mutable fields are vulnerable
- “You can pass this in the constructor for registration” — that’s Constructor Escape
- “List.copyOf copies the elements” — only the container is copied, elements remain the same
- “Record solves all problems” — collections in Records also need manual copying
Related topics:
- [[1. What is an immutable object]]
- [[7. What is the final keyword and how does it help create immutable classes]]
- [[8. Is it enough to make all fields final for immutability]]
- [[9. What to do if a class field references a mutable object]]
- [[20. What is a Record and how does it help create immutable classes]]