SSerguey Asael Shinder
Java coding notes: the JVM, and writing software that lasts

Serguey Asael Shinder: stream.toList() is not collect(Collectors.toList()): one is unmodifiable

· by Serguey Asael Shinder / Serguey Shinder

Since Java 16, Stream has a toList() method, and it is tempting to replace every collect(Collectors.toList()) with it. The two look the same and usually behave the same, until someone adds to the result:

List<String> a = names.stream().filter(n -> !n.isBlank()).collect(Collectors.toList());
a.add("x");   // works today
List<String> b = names.stream().filter(n -> !n.isBlank()).toList();
b.add("x");   // UnsupportedOperationException
Serguey Asael Shinder: stream.toList() is not collect(Collectors.toList()): one is unmodifiable
stream.toList() is not collect(Collectors.toList()): one is unmodifiable — Serguey Asael Shinder

What each one promises.

The three-way difference in one line. toList() is unmodifiable and accepts nulls; toUnmodifiableList() is unmodifiable and rejects them; Collectors.toList() promises nothing about mutability.

Where it bites. The refactoring is usually done mechanically, and the add that fails is often not next to the stream: it is in a method several calls away that received the list and assumed it could append. The failure appears at run time, in a code path the refactoring did not touch.

The rule. If the caller needs to modify the result, say so in the code: collect(Collectors.toCollection(ArrayList::new)), which the toList() javadoc itself points to "if more control over the returned object is required". Use toList() where the list is a read-only result, and let the exception catch anyone who assumed otherwise.

No JDK runs on the machine this note was written on; the behaviour described is the one specified in the linked javadoc.