skip to content

What does Collections.unmodifiableList return, and how is it different from a truly immutable collection like List.of?

level: seniorimportance: should knowfreq 55%

answer

  1. unmodifiable = read-only VIEW, not a copy
  2. mutations to the backing list leak through
  3. List.of/copyOf = truly immutable, reject nulls
  4. wraps with UnsupportedOperationException on mutators
  5. immutability is shallow — elements can still change

basics

~20 s

It returns a read-only view of an existing list: you cannot add or remove through it, but the original list can still be changed and those changes show up. List.of is a separate, fully immutable copy that nobody can change.

solid answer

~40 s

Collections.unmodifiableList wraps an existing list and returns a view that throws UnsupportedOperationException on any mutating method. Crucially it is a view, not a copy: if you still hold a reference to the backing list and modify it, those changes are visible through the wrapper. So it provides read-only access for the recipient, not true immutability. List.of (Java 9+) and List.copyOf create genuinely immutable collections with no mutator path at all, and they also reject null elements and are often more memory-compact. A second subtlety: neither makes the elements themselves immutable — only the structure is protected. For a truly safe read-only handoff you either defensively copy first (unmodifiableList(new ArrayList<>(src))) or use List.copyOf.

code

java · 8 lines
java
List<String> src = new ArrayList<>(List.of("a"));
List<String> view = Collections.unmodifiableList(src);
src.add("b");                 // mutating the backing list...
System.out.println(view);     // [a, b] -- leaked through the view!

List<String> safe = List.copyOf(src); // immutable snapshot
src.add("c");
System.out.println(safe);     // [a, b] -- unaffected

go deeper

for a junior

Knows unmodifiableList prevents add/remove and throws if you try.

for a middle

Explains it is a view whose backing list can still change, and that List.of is a separate immutable option.

for a senior

Contrasts view-vs-copy, null handling, and gives the defensive-copy recipe for safe getters.

for a principal

Distinguishes shallow vs deep immutability, reasons about API contracts/encapsulation, and standardizes List.copyOf in code guidelines.

## The problem these wrappers solve When you return a collection from a method, the caller can normally mutate it — `add`, `remove`, `clear` — which may corrupt your object's internal state. You want to hand out **read-only** access. `Collections.unmodifiableList(list)` (and `unmodifiableSet`, `unmodifiableMap`, etc.) is one tool for that. ## What unmodifiableList actually returns: a VIEW It returns a thin **wrapper object** that delegates every read to the original list and **throws `UnsupportedOperationException`** on every mutator (`add`, `set`, `remove`, `clear`, the iterator's `remove`, etc.). The critical word is **view**: it does **not copy** the data. It still points at the original list. Therefore: ```java List<String> src = new ArrayList<>(List.of("a")); List<String> ro = Collections.unmodifiableList(src); ro.add("b"); // throws UnsupportedOperationException src.add("b"); // allowed! System.out.println(ro); // [a, b] — the change leaked through ``` So `unmodifiableList` makes the collection unmodifiable **through that reference**, but it does **not** make the underlying data immutable. If anyone keeps the original `src`, immutability is an illusion. ## Truly immutable: List.of / List.copyOf (Java 9+) - `List.of(...)` builds a brand-new collection with **no mutator implementation at all** and no backing reference anyone can reach. It is genuinely immutable. - `List.copyOf(src)` snapshots the *current contents* of `src` into a new immutable list; later changes to `src` are invisible. These factories differ from the `unmodifiableXxx` wrappers in three ways: 1. **No leak path** — there is no separate mutable backing list. 2. **They reject `null` elements** (throw `NullPointerException`); `unmodifiableList` permits nulls if the source had them. 3. They are typically **more compact** in memory (specialized internal layouts). ## What "immutable" does NOT cover: shallow vs deep Neither approach makes the *elements* immutable. If a list holds mutable objects (e.g. `StringBuilder`, a mutable `User`), you can still mutate those objects — only the **structure** (which references the list holds) is frozen. This is **shallow** immutability. Deep immutability requires immutable element types too. ## The safe-handoff recipe To return a defensively safe read-only collection from a getter: - Modern code: `return List.copyOf(internalList);` — immutable snapshot, nulls rejected. - Pre-Java 9 or when you must wrap a live list and never expose the original: `return Collections.unmodifiableList(new ArrayList<>(internalList));` — the `new ArrayList<>` copy severs the leak, and the wrapper blocks mutation. ## Key terms - **View:** an object that delegates to an underlying collection without copying; changes to the underlying data are visible. - **UnsupportedOperationException:** thrown when a method is structurally present (from the interface) but intentionally not supported. - **Shallow immutability:** the collection's structure is frozen, but the elements it references may still be mutable. - **Defensive copy:** copying input/output data so external code cannot mutate your internal state.

  • How do you make unmodifiableList truly safe to hand out?
    Wrap a fresh copy: Collections.unmodifiableList(new ArrayList<>(src)), so no one holds a mutable reference to the backing list — or just use List.copyOf.
  • What happens if you call add() on an unmodifiable list?
    It throws UnsupportedOperationException at runtime; the method exists on the List interface but the wrapper refuses to perform it.

saying these in an interview costs you the question

  • Claiming unmodifiableList makes the data immutable
  • Forgetting that holding the original list defeats the wrapper
  • Thinking it deep-freezes the elements
  • Assuming List.of allows null elements

context