How to Create an Immutable Class in Java

Naveen AnamaneniUpdated · 5 min read

A printed bus ticket can't be edited: if you need a different seat, you get a new ticket. An immutable object works the same way. Once it's created, its state never changes; any "change" produces a new object. String, Integer, LocalDate and BigDecimal are all immutable, and that's a big reason they're safe to share everywhere. Writing your own immutable classes follows a short, strict list of rules.

An Immutable Money Class

A change creates a new object; the old one never changes
Immutable.java▶ Try it
public class Immutable {
    static final class Money {
        private final long paise;
        private final String currency;
 
        Money(long paise, String currency) {
            if (paise < 0) {
                throw new IllegalArgumentException("Negative amount");
            }
            this.paise = paise;
            this.currency = currency;
        }
 
        long paise() { return paise; }
        String currency() { return currency; }
 
        Money plus(Money other) {
            if (!currency.equals(other.currency)) {
                throw new IllegalArgumentException("Currency mismatch");
            }
            return new Money(paise + other.paise, currency);
        }
 
        @Override
        public String toString() {
            return currency + " " + paise / 100 + "."
                + String.format("%02d", paise % 100);
        }
    }
 
    public static void main(String[] args) {
        Money ticket = new Money(25000, "INR");
        Money snacks = new Money(12050, "INR");
        Money total = ticket.plus(snacks);
        System.out.println(ticket + " + " + snacks + " = " + total);
        System.out.println("ticket unchanged: " + ticket);
 
        String name = "navika";
        String upper = name.toUpperCase();
        System.out.println(name + " " + upper);
    }
}
Output
INR 250.00 + INR 120.50 = INR 370.50
ticket unchanged: INR 250.00
navika NAVIKA

How it works

  1. The class is final, so no subclass can add mutable state or override methods.
  2. Both fields are private final: set once in the constructor, never again.
  3. There are no setters. The constructor validates, so an invalid Money can never exist.
  4. plus doesn't modify this; it returns a new Money. ticket still holds 250.00 afterwards.
  5. String works the same way: toUpperCase() returns a new string and leaves name unchanged.

The Rules

RuleWhy
Declare the class final (or make constructors private with factories)Subclasses can't break immutability
Make all fields private finalThey can't be reassigned or reached from outside
Provide no setters or other mutating methodsNothing changes state after construction
Validate everything in the constructorInvalid objects never exist
Copy mutable inputs in the constructor, and mutable outputs in gettersCallers can't change internal arrays or lists (see "Defensive Copying in Java")
Don't let this escape during constructionOther code never sees a half-built object

final fields alone aren't enough: a private final List<String> field can't be reassigned, but the list itself can still be changed unless you copy it and hand out unmodifiable views.

Why Immutability Matters

BenefitExplanation
Thread-safe for freeNo thread can change it, so no locking is needed (see the later lessons on multithreading)
Safe as HashMap keys and HashSet membersThe hash code can never change under the collection
Safe to share and cachePass it anywhere without defensive copies; Integer and String caches rely on this
Easy to reason aboutIts state is the state it was born with
Failure atomicityA failed operation can't leave it half-changed

The cost is creating new objects for every change. For most code that's cheap, and for heavy string building there's StringBuilder, covered with strings later in the course.

Common Mistakes

Making fields final but returning an internal list or array. Callers can change its contents.

Forgetting final on the class. A subclass can add setters and pass itself off as the immutable type.

Expecting str.toUpperCase() to change str. Assign the result: str = str.toUpperCase();.

Interview Corner

How do you create an immutable class in Java?

Make the class final, make all fields private and final, provide no setters or mutating methods, initialize and validate all fields in the constructor, defensively copy any mutable objects passed in or returned, and make "modifying" methods return new instances. Records give most of this automatically.

Why is String immutable in Java?

Immutability lets strings be shared safely: the string pool can reuse one object for identical literals, strings are safe as HashMap keys because their hash code can be cached and never changes, they're thread-safe, and security-sensitive values such as file names and class names can't be changed after being checked.

Practice

  1. Easy: Write an immutable Point with x and y, and a method moveBy(int dx, int dy) that returns a new point.
  2. Harder: Write an immutable Exam with a subject name and an int[] marks. Make sure neither the constructor's argument nor the getter can be used to change the marks.

Quick check: Immutable class

4 questions
1Which is required for an immutable class?
2How does an immutable class "change" a value?
3Why make the class final?
4Which JDK class is NOT immutable?

Key Takeaways

Was this tutorial helpful?

Lesson 157 of 482