How to Create an Immutable Class in Java
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
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);
}
}INR 250.00 + INR 120.50 = INR 370.50
ticket unchanged: INR 250.00
navika NAVIKAHow it works
- The class is
final, so no subclass can add mutable state or override methods. - Both fields are
private final: set once in the constructor, never again. - There are no setters. The constructor validates, so an invalid
Moneycan never exist. plusdoesn't modifythis; it returns a newMoney.ticketstill holds 250.00 afterwards.Stringworks the same way:toUpperCase()returns a new string and leavesnameunchanged.
The Rules
| Rule | Why |
|---|---|
Declare the class final (or make constructors private with factories) | Subclasses can't break immutability |
Make all fields private final | They can't be reassigned or reached from outside |
| Provide no setters or other mutating methods | Nothing changes state after construction |
| Validate everything in the constructor | Invalid objects never exist |
| Copy mutable inputs in the constructor, and mutable outputs in getters | Callers can't change internal arrays or lists (see "Defensive Copying in Java") |
Don't let this escape during construction | Other 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
| Benefit | Explanation |
|---|---|
| Thread-safe for free | No thread can change it, so no locking is needed (see the later lessons on multithreading) |
Safe as HashMap keys and HashSet members | The hash code can never change under the collection |
| Safe to share and cache | Pass it anywhere without defensive copies; Integer and String caches rely on this |
| Easy to reason about | Its state is the state it was born with |
| Failure atomicity | A 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
- Easy: Write an immutable
Pointwithxandy, and a methodmoveBy(int dx, int dy)that returns a new point. - Harder: Write an immutable
Examwith a subject name and anint[] marks. Make sure neither the constructor's argument nor the getter can be used to change the marks.
Key Takeaways
Was this tutorial helpful?