toList()
Collect a stream straight into an unmodifiable List, and know how it differs from Collectors.toList() and Collectors.toUnmodifiableList().
default List<T> toList()- Added in Java Java 16
Return value
List<T>An unmodifiable List containing the stream's elements in encounter order. Null elements are allowed.
Errors
Nothing for the call itself. Attempts to modify the returned list throw UnsupportedOperationException.
toList() is a terminal operation added in Java 16 that returns the stream's elements as a List. It is the shortest way to end a pipeline and, for new code, the default choice when you want a list.
The result is unmodifiable
Calls that change the list (add, remove, set, sort, clear) throw UnsupportedOperationException. For values you return from a service or put in a response DTO this is what you want: callers cannot change your data behind your back. When you need to add to the result, ask for a mutable list explicitly:
List<String> mutable = stream.collect(Collectors.toCollection(ArrayList::new));
List<String> copy = new ArrayList<>(stream.toList());Three ways to collect a list
| Call | Modifiable? | Nulls? |
|---|---|---|
stream.toList() (16) | No | Allowed |
collect(Collectors.toList()) (8) | Not specified | Allowed |
collect(Collectors.toUnmodifiableList()) (10) | No | NullPointerException |
Collectors.toList() makes no guarantee about the list type. Current JDKs return an ArrayList, and a lot of code quietly depends on that by calling add on the result. That is the one trap when migrating: replacing collect(Collectors.toList()) with toList() everywhere is safe only where nobody modifies the result. Search for callers that sort or add to the list before changing it.
Nulls
Unlike List.of and toUnmodifiableList, toList() accepts null elements. That is convenient but can hide a bug, such as a failed lookup mapped to null, until something reads the element. Filter nulls out where they are not expected.
Unmodifiable is not deeply immutable
The list cannot change, but the objects in it can if they are mutable. A list of records with immutable fields is effectively immutable; a list of JPA entities is not. Also note that sorting must happen in the stream (sorted() before toList()) or on a copy, because list.sort on the result throws.
Example
When to use it
- Return a list of response DTOs from a service so controllers and serializers cannot modify it.
- Cache a computed list of enabled feature flags that many request threads read.
- Collect validated rows from an upload into a list that is passed unchanged to the persistence layer.
Frequently asked questions
Is Stream.toList() the same as collect(Collectors.toList())?
Not quite. Stream.toList() returns an unmodifiable list, while Collectors.toList() makes no guarantee and in practice returns a mutable ArrayList. Both allow null elements and keep encounter order.
Why does adding to my stream result throw UnsupportedOperationException?
The list came from Stream.toList() or Collectors.toUnmodifiableList(), which are unmodifiable. Collect with Collectors.toCollection(ArrayList::new) or copy it with new ArrayList<>(list) when you need to change it.
Which Java version added Stream.toList()?
Java 16. On Java 8 to 15, use collect(Collectors.toList()), or collect(Collectors.toUnmodifiableList()) on Java 10 and later for an unmodifiable result.
More examples
toUnmodifiableList rejects nulls, toList does not
Both give an unmodifiable list, but only toUnmodifiableList enforces the no-nulls rule of List.of. Use it when a null in the result would be a bug you want caught early.
void main() {
try {
Stream.of("a", null).collect(Collectors.toUnmodifiableList());
} catch (NullPointerException e) {
IO.println("toUnmodifiableList: NullPointerException");
}
List<String> withNull = Stream.of("a", null).toList();
IO.println(withNull.size() + " " + withNull);
}
// Output:
// toUnmodifiableList: NullPointerException
// 2 [a, null]Sort in the stream, not on the result
Sorting changes the list, so it counts as modification and throws. Put sorted() in the pipeline, or sort a mutable copy and leave the original alone.
void main() {
List<Integer> scores = Stream.of(30, 10, 20).toList();
try {
scores.sort(null);
} catch (UnsupportedOperationException e) {
IO.println("cannot sort the toList() result in place");
}
IO.println(scores.stream().sorted().toList());
List<Integer> copy = new ArrayList<>(scores);
Collections.sort(copy);
IO.println(copy + " " + scores);
}
// Output:
// cannot sort the toList() result in place
// [10, 20, 30]
// [10, 20, 30] [30, 10, 20]Returning a list from a service method
Returning an unmodifiable list makes the method's result safe to share and cache. A caller that wants to extend it copies it first, so the service's data never changes unexpectedly.
List<String> activeUserIds(List<String> all) {
return all.stream()
.filter(id -> !id.startsWith("x"))
.toList();
}
void main() {
List<String> ids = activeUserIds(List.of("u1", "x2", "u3"));
IO.println(ids);
// A caller that needs to add makes its own copy
List<String> withAdmin = new ArrayList<>(ids);
withAdmin.add("admin");
IO.println(withAdmin);
IO.println(ids);
}
// Output:
// [u1, u3]
// [u1, u3, admin]
// [u1, u3]
Discussion