Cosmic Module

J

Qubits of DPK

March 15, 2026

Core Java

Layman Explanation

Imagine a birth certificate. Once issued, nobody can change your name, date of birth, or parents on it. It's fixed forever. That's an immutable object — created once, never changed.

Real World Analogy

A printed book. Once printed, the words never change. Everyone reading the same book gets identical content. No one can silently modify chapter 3 while you're reading chapter 5. Safe, reliable, consistent.

What is an Immutable Class?

An immutable class is one whose objects' state cannot be changed after creation.
Java's most famous immutable class: String
java
QUBITS OF DPK
1String name = "Deepak";
2name.concat(" Kumar");     // creates new String, original unchanged
3System.out.println(name);  // still "Deepak"
Other built-in immutable classes:
javascript
QUBITS OF DPK
1String, Integer, Long, Double, Float,
2Boolean, Byte, Character, Short
3BigDecimal, BigInteger
4LocalDate, LocalDateTime (Java 8+)
5UUID

Rules to Create an Immutable Class

There are 5 mandatory rules:

Rule 1 — Declare class as final

java
QUBITS OF DPK
1public final class Money {  // ✅ cannot be subclassed
Prevents a subclass from overriding methods and breaking immutability.

Rule 2 — All fields must be private final

java
QUBITS OF DPK
1private final BigDecimal amount;   // ✅ private = no direct access
2private final String currency;     // ✅ final = set once in constructor

Rule 3 — No setters

java
QUBITS OF DPK
1// NO setAmount(), NO setCurrency() — don't provide any

Rule 4 — Initialize all fields in constructor

java
QUBITS OF DPK
1public Money(BigDecimal amount, String currency) {
2    this.amount   = amount;
3    this.currency = currency;
4}

Rule 5 — Defensive copy for mutable fields

java
QUBITS OF DPK
1// If field is mutable (like Date, List, arrays)
2// copy it in constructor AND in getter
3private final Date createdAt;
4
5public Money(BigDecimal amount, String currency, Date createdAt) {
6    this.amount    = amount;
7    this.currency  = currency;
8    this.createdAt = new Date(createdAt.getTime());  // ✅ defensive copy in constructor
9}
10
11public Date getCreatedAt() {
12    return new Date(createdAt.getTime());  // ✅ defensive copy in getter
13}

Complete Immutable Class Example

java
QUBITS OF DPK
1public final class Money {
2    private final BigDecimal amount;
3    private final String currency;
4    private final List<String> tags;  // mutable field — needs defensive copy
5
6    public Money(BigDecimal amount, String currency, List<String> tags) {
7        if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
8            throw new IllegalArgumentException("Amount must be non-negative");
9        }
10        if (currency == null || currency.isBlank()) {
11            throw new IllegalArgumentException("Currency required");
12        }
13        this.amount   = amount;
14        this.currency = currency;
15        this.tags     = List.copyOf(tags);  // defensive copy — unmodifiable
16    }
17
18    // Only getters — no setters
19    public BigDecimal getAmount()   { return amount; }
20    public String     getCurrency() { return currency; }
21    public List<String> getTags()   { return tags; }  // List.copyOf is already unmodifiable
22
23    // Instead of setters — return NEW object with changed value
24    public Money add(Money other) {
25        if (!this.currency.equals(other.currency)) {
26            throw new IllegalArgumentException("Currency mismatch");
27        }
28        return new Money(this.amount.add(other.amount), this.currency, this.tags);
29    }
30
31    public Money withCurrency(String newCurrency) {
32        return new Money(this.amount, newCurrency, this.tags);
33    }
34
35    @Override
36    public boolean equals(Object obj) {
37        if (this == obj) return true;
38        if (!(obj instanceof Money)) return false;
39        Money other = (Money) obj;
40        return amount.equals(other.amount) && currency.equals(other.currency);
41    }
42
43    @Override
44    public int hashCode() { return Objects.hash(amount, currency); }
45
46    @Override
47    public String toString() { return amount + " " + currency; }
48}

Why Immutable Classes Are Valuable

1. Thread Safety (FREE)

java
QUBITS OF DPK
1// Immutable objects can be safely shared across threads
2// No synchronization needed!
3Money price = new Money(new BigDecimal("99.99"), "USD", List.of());
4
5// Thread 1
6price.getAmount();  // safe
7// Thread 2
8price.getAmount();  // safe — same object, no modification possible

2. Safe as Map Keys and Set Elements

java
QUBITS OF DPK
1// HashMap/HashSet rely on stable hashCode
2// Mutable keys can "lose" entries if modified after insertion
3Map<Money, String> priceMap = new HashMap<>();
4Money key = new Money(new BigDecimal("100"), "USD", List.of());
5priceMap.put(key, "premium");
6// Since Money is immutable, key's hashCode never changes — always findable

3. Easy to Cache (Memoize)

java
QUBITS OF DPK
1// Safe to cache results — object never changes
2private static final Money ZERO_USD =
3    new Money(BigDecimal.ZERO, "USD", List.of());

4. Failure Atomicity

java
QUBITS OF DPK
1// If constructor throws, no partially-constructed object is visible
2// Either fully created or not created at all

Java Records — Immutable by Design (Java 16+)

java
QUBITS OF DPK
1// Records are automatically immutable!
2public record Money(BigDecimal amount, String currency) {
3    // Compact constructor for validation
4    Money {
5        if (amount.compareTo(BigDecimal.ZERO) < 0)
6            throw new IllegalArgumentException("Negative amount");
7    }
8}
9
10Money price = new Money(new BigDecimal("99.99"), "USD");
11price.amount();    // 99.99
12price.currency();  // USD
13// Cannot modify — all fields are private final automatically

Production Use Cases

java
QUBITS OF DPK
1// 1. Value Objects in Domain-Driven Design (DDD)
2public final class Email {
3    private final String value;
4    public Email(String value) {
5        if (!value.contains("@")) throw new IllegalArgumentException("Invalid email");
6        this.value = value.toLowerCase();
7    }
8    public String getValue() { return value; }
9}
10
11// 2. Configuration objects
12public final class DatabaseConfig {
13    private final String url, username;
14    private final int maxPoolSize;
15    // set once from properties file, never changes
16}
17
18// 3. Event objects in event sourcing
19public final class OrderPlacedEvent {
20    private final String orderId;
21    private final BigDecimal total;
22    private final Instant occurredAt;
23    // Events are immutable records of what happened
24}
25
26// 4. Cache keys — hashCode stability guaranteed
27public final class CacheKey {
28    private final String service;
29    private final Long entityId;
30}

️ All Traps

java
QUBITS OF DPK
1// Trap 1 — Not making class final
2public class Money {
3    private final BigDecimal amount;
4    // Someone can subclass and add setters!
5}
6public class MutableMoney extends Money {
7    private BigDecimal mutableAmount;  // bypasses immutability!
8}
9
10// Trap 2 — Mutable field without defensive copy
11public final class Period {
12    private final Date startDate;
13    Period(Date start) { this.startDate = start; }  // ❌ direct reference!
14    public Date getStartDate() { return startDate; } // ❌ caller can mutate!
15}
16Date d = new Date();
17Period p = new Period(d);
18d.setTime(0);           // 💥 mutates p's start date!
19p.getStartDate().setTime(0);  // 💥 directly mutates internals!
20
21// Fix: defensive copy in both constructor and getter
22Period(Date start) { this.startDate = new Date(start.getTime()); }  // ✅
23public Date getStartDate() { return new Date(startDate.getTime()); } // ✅
24
25// Trap 3 — final field with mutable object
26public final class Container {
27    private final List<String> items;  // final reference, but list is MUTABLE!
28    Container(List<String> items) { this.items = items; }  // ❌
29    public List<String> getItems() { return items; }       // ❌ caller can modify!
30}
31// Fix: use Collections.unmodifiableList() or List.copyOf()
32
33// Trap 4 — Immutability ≠ performance
34// Every "modification" creates a new object
35// For frequent changes, consider builder pattern or mutable alternatives

🆚 Mutable vs Immutable

30-Second Interview Answer

"An immutable class is one whose state cannot change after creation. The 5 rules: declare class final (prevent subclassing), all fields private final (no reassignment), no setters, initialize all fields in constructor, and defensive copy for mutable fields (Date, List, arrays) in both constructor and getter. Benefits: thread safety without synchronization, safe as HashMap keys (stable hashCode), easy caching, and failure atomicity. String, Integer, and all wrapper classes are immutable. Java Records (Java 16+) provide immutability automatically with less boilerplate."

Interview Questions & MAANG-Level Answers

Q1. What are the rules to create an immutable class?
Five rules: (1) final class — prevents subclass from breaking immutability by adding setters. (2) private final fields — private prevents direct access, final prevents reassignment. (3) No setters — no modification after construction. (4) Initialize all fields in constructor — only time values are set. (5) Defensive copy for mutable fields — Date, arrays, List must be copied in constructor (so original can't affect object) and in getters (so caller can't modify internals). Miss any one rule and immutability is compromised.
Q2. Why is String immutable in Java?
Three reasons: (1) String Pool — multiple references share same object safely only if it can't be mutated. If a could change "hello", all references to that pool object would see the change. (2) Thread safety — String is used everywhere (DB URLs, passwords, class names) and needs to be safely shared across threads without synchronization. (3) Security — ClassLoader uses String names; if a String could be mutated after permission check but before use, a malicious class could be loaded. Also enables hashCode caching since value never changes.
Q3. What is the difference between final and immutable?
final on a variable means the variable cannot be reassigned — but if it holds a mutable object, the object itself can still be modified:
java
QUBITS OF DPK
1final List<String> list = new ArrayList<>();
2list.add("hello");   // ✅ allowed! final means list can't point elsewhere
3list = new ArrayList<>();  // ❌ can't reassign the reference
Immutability means the object's state cannot change, regardless of how many references point to it. final is about the reference; immutable is about the object.
Q4. How does immutability help with thread safety?
Immutable objects have no state that can change, so concurrent reads by multiple threads are completely safe — no synchronization needed. If Thread A and Thread B both read Money.getAmount() simultaneously, they always get the same value. No race conditions possible. This is why the JVM internally uses immutable String for class names, package names, and constant pool entries. In concurrent systems design, "shared nothing" and "shared immutable" are the two safest patterns.
Q5. What is a defensive copy and when is it needed?
A defensive copy is creating a copy of a mutable object instead of sharing the reference, to prevent external code from modifying internals:
java
QUBITS OF DPK
1// Without defensive copy:
2public Period(Date start, Date end) {
3    this.start = start;  // shares reference — caller can mutate!
4    this.end   = end;
5}
6
7// With defensive copy:
8public Period(Date start, Date end) {
9    this.start = new Date(start.getTime());  // independent copy
10    this.end   = new Date(end.getTime());
11}
12public Date getStart() { return new Date(start.getTime()); }  // copy in getter too
Needed for: Date, arrays, ArrayList, HashMap — any mutable object stored in an immutable class. Java 8+ LocalDate/LocalDateTime are immutable themselves — no defensive copy needed.