Back to Immutability
Q3Question 3 of 4

How do you make an existing List immutable?

Key Concepts
  • List.of(a, b, c) creates a truly immutable list (Java 9+)
  • List.copyOf(existingList) creates an immutable copy (Java 10+)
  • Collections.unmodifiableList(list) creates a VIEW – the original can still be modified!
  • Any add/remove/set on an immutable list throws UnsupportedOperationException
  • Understanding this concept is essential for Java interviews at senior level

Definition

List.of() and List.copyOf() create truly immutable lists. Collections.unmodifiableList() is only an unmodifiable view – the underlying list can still change.

Three approaches:

  • List.of(a, b, c) – truly immutable. No null elements allowed. Throws on modification.
  • List.copyOf(list) – copies the list into an immutable version. Modifications to the original do not affect the copy.
  • Collections.unmodifiableList(list) – wraps the original. Prevents modification through the wrapper, but the original list can still be modified, and the wrapper reflects those changes.

List.of is a printed, laminated bank statement – truly unmodifiable. Collections.unmodifiableList is a glass cover over the original statement – you cannot write on it, but someone with access to the original underneath can still change it.

Understanding the Concepts

List.of() – truly immutable (Java 9+)

List.of() creates a completely immutable list. The returned list does not allow add, remove, set, or any other structural modification. It also rejects null elements at creation time.

JAVA
List<String> accounts = List.of("SAV2001", "CUR3001", "LOAN5001");
// accounts.add("NEW");  // throws UnsupportedOperationException
// accounts.set(0, "X"); // throws UnsupportedOperationException
// List.of("SAV2001", null) → throws NullPointerException at creation

The list returned by List.of() is not an ArrayList. It is an internal implementation that stores elements in a compact, space-efficient form.

List.copyOf() – immutable copy (Java 10+)

List.copyOf() creates a new immutable list that is a snapshot of the original. Changes to the original list do not affect the copy.

JAVA
List<String> mutable = new ArrayList<>(List.of("SAV2001", "CUR3001"));
List<String> immutable = List.copyOf(mutable);

mutable.add("LOAN5001");  // modifies the original
System.out.println(immutable);  // [SAV2001, CUR3001] – unaffected!
System.out.println(mutable);    // [SAV2001, CUR3001, LOAN5001]

If the source list is already an immutable List.of(), List.copyOf() may return the same instance without copying, which is an optimization.

Collections.unmodifiableList() – view only

Collections.unmodifiableList() wraps the original list in a read-only view. The wrapper rejects modifications, but the underlying list can still be changed directly, and those changes are visible through the wrapper.

JAVA
List<String> original = new ArrayList<>(List.of("SAV2001"));
List<String> view = Collections.unmodifiableList(original);

// view.add("NEW");  // throws UnsupportedOperationException
original.add("CUR3001");  // modifies the original directly
System.out.println(view);  // [SAV2001, CUR3001] – the view changed!

This is why Collections.unmodifiableList is not truly immutable – it is a read-only wrapper over a mutable list. If any code still holds a reference to the original list, it can mutate the data.

Comparison

| Method | Truly immutable? | Null elements? | Original can modify? |
|---|---|---|---|
| List.of(...) | Yes | No (NPE) | N/A (no original) |
| List.copyOf(list) | Yes | No (NPE) | No (independent copy) |
| Collections.unmodifiableList(list) | No (view) | Yes | Yes (view changes) |

How does this apply to banking code?

In a banking application, you often need to return a list of transactions or account summaries from a service method. Returning a mutable list lets the caller accidentally modify internal state.

JAVA
package com.bank.service;

import java.util.List;
import java.util.ArrayList;

public class AccountService {
    private final List<String> activeAccountIds = new ArrayList<>();

    // WRONG: exposes internal mutable list
    public List<String> getActiveAccounts() {
        return activeAccountIds;  // caller can add/remove!
    }

    // CORRECT: return an immutable copy
    public List<String> getActiveAccountsSafe() {
        return List.copyOf(activeAccountIds);  // caller cannot modify
    }
}

This is a defensive copy pattern. The safe version ensures that no external code can corrupt the service's internal data.

What exceptions are thrown?

All three immutable approaches throw UnsupportedOperationException on structural modifications. List.of() and List.copyOf() additionally throw NullPointerException if any element is null.

JAVA
// UnsupportedOperationException on modification
List<String> immutable = List.of("SAV2001");
try {
    immutable.add("CUR3001");
} catch (UnsupportedOperationException e) {
    System.out.println("Cannot modify immutable list");
}

// NullPointerException on null element
try {
    List<String> bad = List.of("SAV2001", null);
} catch (NullPointerException e) {
    System.out.println("Null elements not allowed in List.of()");
}

What should a senior engineer know?

  • Map.of(), Set.of() follow the same pattern as List.of(). Map.of() takes alternating key-value pairs: Map.of("SAV2001", account1, "CUR3001", account2).
  • Guava ImmutableList.Builder provides a builder for constructing immutable lists incrementally: ImmutableList.builder().add("SAV2001").add("CUR3001").build().
  • Stream.toList() (Java 16+) returns an unmodifiable list. It is similar to List.copyOf(stream.collect(toList())) but allows null elements.
  • Immutable lists are value-based. Do not rely on identity (==); use equals() to compare them.
  • Thread safety: Truly immutable lists are inherently thread-safe. You can share them across threads without synchronization.

Visual Overview

DIAGRAM
  List.of("A", "B")            → immutable, independent
  List.copyOf(mutableList)     → immutable copy, independent
  Collections.unmodifiableList → read-only VIEW of the original (not independent!)

  Any modification → UnsupportedOperationException

Key Takeaway

Use List.of() or List.copyOf() for truly immutable lists. Collections.unmodifiableList() is only a view – modifications to the underlying list are still visible through it.

One sentence to remember: List.of() is truly immutable, Collections.unmodifiableList is just a read-only view over a still-mutable list.

---

Share
Use ← → arrow keys to navigate