Question 13 · Section 13

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...

Language versions: English Russian Ukrainian

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()) — throws UnsupportedOperationException
  • 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 getterunmodifiableList to save memory (if the client can’t reach the original)
  • In the constructorList.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

  • unmodifiableList is 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() and List.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
  • unmodifiableList does not make the list thread-safe — need CopyOnWriteArrayList or synchronizedList
  • 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 synchronizedList or CopyOnWriteArrayList
  • “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]]