toList()

Collect a stream straight into an unmodifiable List, and know how it differs from Collectors.toList() and Collectors.toUnmodifiableList().

Signature
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

CallModifiable?Nulls?
stream.toList() (16)NoAllowed
collect(Collectors.toList()) (8)Not specifiedAllowed
collect(Collectors.toUnmodifiableList()) (10)NoNullPointerException

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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · java
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.

Example · java
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.

Example · java
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

  • Be the first to comment on this lesson.