Stream toList Collector
Collecting a stream into a `List` is the most common terminal operation in modern Java. This snippet shows the three idiomatic options: the legacy `Collectors.toList()`, the unmodifiable `Collectors.toUnmodifiableList()` (Java 10+), and the convenient `Stream.toList()` shortcut (Java 16+). Pick the unmodifiable variant for return values to prevent caller mutation.
196 views
2
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = nums.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println(squares);
// Returned list type is ArrayList; mutation is allowed.
squares.add(36);
System.out.println(squares);
}
}Collectors.toList() returns a mutable ArrayList carrying the stream's results. It is the workhorse collector that has shipped since Java 8 and is still useful when you need to keep adding to or sorting the list afterwards. The lambda n -> n * n is mapped over the stream, then materialised into a list. Don't return this list from a public method without copying or wrapping with Collections.unmodifiableList(...) if callers should not mutate it.
import java.util.*;
import java.util.stream.*;
public class Main {
static List<String> activeUserNames(List<String> all) {
return all.stream()
.filter(s -> !s.startsWith("_"))
.map(String::toLowerCase)
.collect(Collectors.toUnmodifiableList());
}
public static void main(String[] args) {
List<String> users = activeUserNames(Arrays.asList("Ada", "_Bot", "Linus"));
System.out.println(users);
try {
users.add("hacker");
} catch (UnsupportedOperationException e) {
System.out.println("caller cannot mutate the returned list");
}
}
}Collectors.toUnmodifiableList() (Java 10+) wraps the result in a list whose mutators throw UnsupportedOperationException. Reach for this in API boundaries where you do NOT want callers mutating internal state. It also enables some downstream optimisations because the runtime knows the list will not change. Note that the elements themselves are not deeply frozen, only the list structure is locked.
import java.util.*;
import java.util.stream.*;
// On JDK 16+, the same pipeline is just .toList():
//
// var squares = nums.stream().map(n -> n * n).toList();
//
// Stream.toList() returns an unmodifiable list backed by an array.
// On JDK 13 the equivalent is Collectors.toUnmodifiableList(), shown below.
public class Main {
public static void main(String[] args) {
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
// JDK 13 equivalent of nums.stream().map(...).toList()
List<Integer> doubled = nums.stream()
.map(n -> n * 2)
.collect(Collectors.toUnmodifiableList());
System.out.println(doubled);
System.out.println("size=" + doubled.size());
}
}Java 16 added Stream.toList() directly on the stream, returning an unmodifiable List. It is the preferred form going forward because it is shorter and signals intent. The runtime difference from Collectors.toUnmodifiableList() is negligible. On older JDKs (13 in our test runner here), use Collectors.toUnmodifiableList() and migrate when your project bumps. Both forms preserve encounter order and behave like the upstream stream regarding nulls.
