Stream.toList vs collect(toList()) in Java
Unmodifiable vs no guarantee.
collect(Collectors.toList()) into the shorter .toList(). Everything compiles. Then a month later, production throws UnsupportedOperationException. What changed?Three ways to end with a list
**stream.toList() (Java 16): unmodifiable**, allows null elements. **collect(Collectors.toList()): no guarantee about type or mutability. collect(Collectors.toUnmodifiableList()): unmodifiable, rejects null**.
Changing a toList() result
What happens?
List<Integer> a = Stream.of(1, 2).toList();
a.set(0, 9);
System.out.println(a);[9, 2]Compile errorThrows UnsupportedOperationException
Show the answer
It compiles — List has set — but the list from toList() is unmodifiable, so set (or add, remove) throws UnsupportedOperationException at runtime.
Works today, promised never
Collectors.toList() currently returns an **ArrayList**, so add happens to work. But its spec makes no promise about mutability — relying on it is relying on an implementation detail.
Needing a list you can change
List<String> names = users.stream()
.map(User::name)
.toList();
names.add("admin"); // throwstoUnmodifiableList() and List.copyOf(...) are unmodifiable too.
List<String> names = users.stream()
.map(User::name)
.collect(Collectors.toCollection(
ArrayList::new));
names.add("admin"); // finetoCollection(ArrayList::new) explicitly promises a mutable ArrayList.
The null difference
Both are "unmodifiable", but they disagree about null: **Stream.toList() accepts nulls, while Collectors.toUnmodifiableList() throws NullPointerException**.
Stream.of("a", null).toList(); // [a, null]
Stream.of("a", null).collect(
Collectors.toUnmodifiableList()); // NPEThe refactoring trap
IDEs and linters may suggest replacing collect(Collectors.toList()) with toList(). It's usually a good change — unless some caller later adds to or sorts the list in place. Then the bug hides until that code path runs in production.
Key takeaways
- stream.toList(): unmodifiable, allows null elements
- Collectors.toList(): no guarantee of type or mutability
- Collectors.toUnmodifiableList(): unmodifiable, rejects null
- Need mutability? collect(toCollection(ArrayList::new))
Stream.toList() was one of the most requested additions to the Streams API: it took until Java 16 — seven years after streams arrived in Java 8.
Practice questions
What does this print?
List<String> a = Stream.of("x").toList();
a.add("y");
System.out.println(a);- [x, y]
- [x]
- Throws UnsupportedOperationException
- Compile error
Check your answer
Throws UnsupportedOperationException. The code compiles because List has add(), but the list returned by toList() is unmodifiable, so add throws at runtime.
What does this print?
List<String> b = Stream.of("x")
.collect(Collectors.toList());
b.add("y");
System.out.println(b);- [x, y]
- [x]
- Throws UnsupportedOperationException
- [y]
Check your answer
[x, y]. Today Collectors.toList() happens to return an ArrayList, so add works. But the spec makes no guarantee — use toCollection(ArrayList::new) when you rely on mutability.