What is Collections.unmodifiableList() and how does it work?
Collections.unmodifiableList() is a method that creates a "read-only" wrapper — an intermediary object that forwards all calls to the original list, but blocks modification meth...
Basic Level
Collections.unmodifiableList() is a method that creates a “read-only” wrapper — an intermediary object that forwards
all calls to the original list, but blocks modification methods.
List<String> list = new ArrayList<>();
list.add("A");
List<String> readOnly = Collections.unmodifiableList(list);
System.out.println(readOnly.get(0)); // "A" — can read
readOnly.add("B"); // ERROR! UnsupportedOperationException
Important: this is a wrapper, not a copy. If the original list is changed, the changes will be visible through the wrapper.
Intermediate Level
How it works
The method returns an object of the internal class Collections.UnmodifiableList, which:
- Reading (
get(),size(),contains()) — delegates to the original list - Writing (
add(),remove(),clear()) — throwsUnsupportedOperationException - Iterator — also wrapped,
remove()is blocked
The main trap
List<String> original = new ArrayList<>();
original.add("A");
List<String> readOnly = Collections.unmodifiableList(original);
original.add("B"); // Changing the original
System.out.println(readOnly); // [A, B] — changes are visible!
UnmodifiableList vs List.copyOf()
| Characteristic | unmodifiableList |
List.copyOf |
|---|---|---|
| Copying | No | Yes |
| Link to original | Preserved | Broken |
| Allows null | Yes | No (NPE) |
| Creation speed | O(1) | O(n) |
| Thread-safety | No | No |
// synchronizedList provides thread-safety, but NOT immutability. // unmodifiableList provides immutability, but NOT thread-safety.
When to use
- In a getter —
unmodifiableListto save memory (if the client can’t reach the original) - In the constructor —
List.copyOf()for true protection
Advanced Level
Decorator pattern under the hood
UnmodifiableList is a classic example of the Decorator Pattern:
// Simplified
class UnmodifiableList<E> implements List<E> {
private final List<E> list; // reference to original
public E get(int index) { return list.get(index); } // delegation
public boolean add(E e) { throw new UnsupportedOperationException(); }
}
Performance
- Creation: O(1) — no copying
- Reading: O(1) — direct call to original
- Memory: minimal — only the wrapper
Thread-safety nuance
unmodifiableList does not make the list thread-safe. If the original list is modified by another thread, you may see ConcurrentModificationException or an inconsistent state. For thread-safety, you need CopyOnWriteArrayList or Collections.synchronizedList().
Warning: access to the original
If the calling code can retain a reference to the original list, unmodifiableList will not provide protection. The caller can change the original directly!
Summary for Advanced
unmodifiableListis a View, not a copy- Efficient way (O(1)) to restrict access rights to a collection
- Does not protect against changes to the original collection
- For true immutability,
List.of()andList.copyOf()are preferable
Interview Cheat Sheet
Must know:
unmodifiableList— wrapper (Decorator Pattern), not a copy: O(1) creation, link to original- Reading delegates to original, writing throws
UnsupportedOperationException - Iterator is also wrapped —
remove()is blocked - Main trap: changes to the original are visible through the wrapper
unmodifiableListdoes not make the list thread-safe — needCopyOnWriteArrayListorsynchronizedList- If the caller retains a reference to the original — unmodifiableList provides no protection
Frequent follow-up questions:
- UnmodifiableList vs List.copyOf? — UnmodifiableList — wrapper (O(1), linked), List.copyOf — copy (O(n), no link)
- Can you add an element through the wrapper? — No,
UnsupportedOperationException - Is UnmodifiableList thread-safe? — No, only blocks writing; if the original is changed by another thread — ConcurrentModificationException
- When to use unmodifiableList? — In a getter, if the client can’t reach the original
Red flags (DO NOT say):
- “UnmodifiableList is a copy” — it’s a wrapper, the original is visible
- “UnmodifiableList = thread-safe” — no, need
synchronizedListorCopyOnWriteArrayList - “synchronizedList provides immutability” — no, only thread-safety
- “Iterator of unmodifiableList allows remove()” — the iterator is also wrapped, remove() is blocked
Related topics:
- [[10. What is a defensive copy]]
- [[11. When should you make a defensive copy]]
- [[12. How to protect a collection from modification]]
- [[29. How to properly work with collections in immutable classes]]