Question 3 · Section 13

How to create an immutable class in Java?

To create an immutable class, follow these steps:

Language versions: English Russian Ukrainian

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

  1. Don’t provide mutator methods
  2. Ensure the class cannot be extended (final)
  3. Make all fields final
  4. Make all fields private
  5. 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 final is not enough for mutable reference types
  • Always use List.copyOf() or Collections.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 this during 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 this before 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]]