Collections.unmodifiableList vs List.of vs List.copyOf
Fast recall
Quick revision
Core answer
Understanding of immutability vs unmodifiability, and the implications of view-based vs copy-based approaches
Thinking Collections.unmodifiableList creates a truly immutable list — the underlying list can still be mutated through the original reference, which is a common source of bugs
Distinguish structural immutability from deep immutability. Note that List.of and List.copyOf are truly immutable (no backing list), more memory-efficient, and null-hostile. Mention that List.of uses compact array-based implementations tuned for small lists.
Interview-ready guide
Interview answer
Java provides several ways to create lists that resist modification — List.of(), List.copyOf(), and Collections.unmodifiableList() — but they differ in an important way: some create an independent copy, others wrap the original and let changes leak through.
Collections.unmodifiableList():
- Wraps the original — mutation methods throw
UnsupportedOperationException - Changes to the original list are still visible through the wrapper — it's a live view, not a copy
- Allows null elements if the original has them
List.of() and List.copyOf():
List.of(): truly immutable, no backing collection; prohibits null elements (NPE immediately)List.copyOf(): creates an immutable copy of an existing collection; returns the same instance if already immutable- Both reject null — safer for API boundaries where null elements indicate bugs
Stream.toList() vs Collectors.toList():
- Java 16
Stream.toList()returns unmodifiable;Collectors.toList()returns mutable ArrayList - Switching between them silently breaks code that modifies the collected result
I recommend List.copyOf() for defensive copies at API boundaries, List.of() for constant lists, and unmodifiableList() only when you intentionally need a live read-only view into a mutable original.
Collections.unmodifiableList vs List.of vs List.copyOf
Direct Answer
Collections.unmodifiableList is a view — the underlying list can still change through the original reference. List.of creates a truly immutable list with no backing mutable list, is null-hostile, and is tuned for small lists. List.copyOf creates an immutable copy and also rejects nulls.
Deep Dive & Architecture
compute, merge, computeIfAbsent, and computeIfPresent are Map methods (Java 8+) that atomically combine get + check + put into a single operation, replacing the verbose get-check-put anti-pattern. computeIfAbsent(key, mappingFunction) — if key absent, compute value and put; most-used for lazy initialization of multi-valued maps: map.computeIfAbsent(key, k -> new ArrayList<>()).add(value). computeIfPresent(key, remappingFunction) — if key present, recompute value (or remove if null returned). compute(key, remappingFunction) — recompute regardless of presence (function gets old value or null). merge(key, value, remappingFunction) — if absent, put value; if present, apply function to combine: map.merge(word, 1, Integer::sum) is the idiomatic word-counter. Why they matter: (1) atomic in ConcurrentHashMap — no lost updates from concurrent get-check-put races; (2) less code than if (map.containsKey(k)) map.put(k, ...) else map.put(k, ...); (3) cleaner intent — the operation says what it does. Pitfalls: (1) not atomic in non-concurrent maps (HashMap) — only the API is single-call, not thread-safe; (2) returning null from a remapping function removes the key — surprising; (3) don't recursively modify the map inside the function — undefined behavior; (4) merge doesn't handle null values the same way as compute — read the Javadoc. For interviews: lead with 'replaces get-check-put, atomic in ConcurrentHashMap, idiomatic for multimap lazy-init and counters'.
Collections.unmodifiableList is a view — the underlying list can still change through the original reference. List.of creates a truly immutable list with no backing mutable list, is null-hostile, and is tuned for small lists. List.copyOf creates an immutable copy and also rejects nulls.
A common mistake is confusing the interface with the implementation (e.g., saying List when you mean ArrayList), or not knowing that Map does not extend Collection.
Common Mistakes & Pitfalls
Thinking Collections.unmodifiableList creates a truly immutable list — the underlying list can still be mutated through the original reference, which is a common source of bugs
Check your understanding
Answer these without looking back at the article.
- What is the Java Collections Framework (JCF) and why does it exist? It provides a unified architecture of interfaces and implementations for storing and manipulating groups of objects, replacing the ad-hoc pre-JCF classes (Vector, Hashtable, Enumeration) with a consistent, interoperable API.
- How does HashSet work internally in Java?
- How does HashMap internally handle hash collisions in Java 8+? Explain the transition from linked list to tree.
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.