Collections Unmodifiable Example in Java
Problem
Collections.unmodifiableList() returns a read-only VIEW of an existing mutable list, not an independent immutable copy — the wrapper blocks direct mutation, but changes made through the original list still show up in the view.
Wrap a mutable List so it can't be modified directly, and confirm changes to the original list still appear through the wrapper.
Java Program
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsUnmodifiableDemo {
public static void main(String[] args) {
List<String> mutable = new ArrayList<>(List.of("Apple", "Banana"));
List<String> view = Collections.unmodifiableList(mutable); // wraps mutable, doesn't copy it
mutable.add("Cherry"); // changing the original still shows up through the view
System.out.println("View after mutating original: " + view);
try {
view.add("Date");
} catch (UnsupportedOperationException e) {
System.out.println("view.add() blocked: UnsupportedOperationException");
}
}
}Output
Core Logic
Wrapping the original list, instead of copying it, means the wrapper has no data of its own — it always reflects whatever the original list currently contains.
- 1
Collections.unmodifiableList(mutable)doesn't copymutable's elements — it returns a thin wrapper that delegates every read straight through tomutable. - 2Calling
view.add(...)directly throwsUnsupportedOperationException, since the wrapper's mutating methods are deliberately disabled. - 3But calling
mutable.add("Cherry")on the ORIGINAL list still works fine —mutableitself was never made immutable, only theviewreference blocks mutation. - 4Reading
viewagain after that shows"Cherry"included, sinceviewwas never a separate snapshot — it's still looking at the same underlying list.
mutable.add("Cherry"), printing view shows [Apple, Banana, Cherry], even though nothing was ever added to view directly.Key Point: This is the key difference from List.of(): List.of() has no mutable original anywhere to leak changes from, while Collections.unmodifiableList() only blocks mutation through the wrapper itself — the backing list underneath is still fully mutable to anyone holding a reference to it.
Why: unmodifiableList() just wraps the existing list in a thin delegating object — it doesn't copy any of the n elements, so wrapping itself costs nothing proportional to the list's size.