library
intermediateCollections.unmodifiable* vs List.of() vs List.copyOf()
Choose the right unmodifiable/immutable collection factory and understand the differences in mutability guarantees.
Three ways to get a read-only list, with three different guarantees. "Unmodifiable" is not "immutable".
Collections.unmodifiableList = putting a 'DO NOT TOUCH' sign on a whiteboard (someone can still erase it from behind). List.of = a printed poster (truly cannot be changed).
Key Concepts
1
List<String> src = new ArrayList<>(List.of("a"));2
var view = Collections.unmodifiableList(src); // a VIEW of src
var fixed = List.of("a"); // truly immutable
var copy = List.copyOf(src); // an immutable copy3
The difference that causes bugs.
src.add("b");
view.size(); // 2 -- the view sees it
copy.size(); // 1 -- the copy does not4
Collections.unmodifiableList wraps the original. Mutating through the wrapper throws, but the backing
list is still mutable by anyone holding it, and those changes show through. So returning one from a getter
does not protect your state.
Collections.unmodifiableList
5
public List<Item> items() {
return Collections.unmodifiableList(items); // caller cannot add...
} // ...but sees every change6
List.copyOf is what you want for defensive copying. It snapshots, so the caller has data that cannot
change under them.
List.copyOf
7
List.of has no backing collection at all. Nothing else holds a reference to its array, so there is
nothing that could modify it.
List.of
8
The null rules differ, and this bites.
List.of("a", null) NullPointerException
List.copyOf(listWithNull) NullPointerException
Collections.unmodifiableList(listWithNull) fine
Arrays.asList("a", null) fine9
Arrays.asList is the odd one out — fixed size, but not read-only:
Arrays.asList
10
var l = Arrays.asList("a", "b");
l.set(0, "z"); // allowed
l.add("c"); // UnsupportedOperationException11
It is a view over the array, so set writes through to it. Passing it to new ArrayList<>(...) is the usual
way to get a real list.
setnew ArrayList<>(...)
12
A small optimisation worth knowing. List.copyOf may return the same instance when the source is
already an immutable list, so copying one twice costs nothing.
List.copyOf
13
The map and set equivalents.
Map.of(k, v, ...) up to 10 pairs
Map.ofEntries(Map.entry(k, v), ...) for more
Set.of(...) Map.copyOf(...) Set.copyOf(...)14
What the interviewer is probing.1. "Is Collections.unmodifiableList immutable?" Probing: the distinction the question turns
on. Stalls: "Yes." Moves up: no — it is an unmodifiable view; the backing list can still be
changed by anyone holding it, and the change shows through.
15
2. "Which should a getter return to protect internal state?" Probing: defensive copying.
Stalls: "The unmodifiable view." Moves up: List.copyOf — the view prevents the caller mutating
through it but still exposes every change you make.
16
3. "How do the null rules differ?" Probing: a real source of NPEs on migration. Stalls:
"They are the same." Moves up: List.of and copyOf reject null; Collections.unmodifiableList and
Arrays.asList allow it.
17
4. "What is Arrays.asList actually?" Probing: the odd one out. Stalls: "An immutable list."
Moves up: a fixed-size view over the array — set writes through to the array, while add and remove
throw.