
Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:
Once the early-adopter seats are all used, the price will go up and stay at $33/year.
Last updated: April 4, 2025
We often wish to convert a Java Stream into a collection. This usually results in a mutable collection, but we can customize it.
In this short tutorial, we’re going to take a close look at how to collect a Java Stream to an immutable collection – first using plain Java, and then using the Guava library.
Starting with Java 10, we can use the toUnmodifiableList method from Java’s Collectors class:
List<String> givenList = Arrays.asList("a", "b", "c");
List<String> result = givenList.stream()
.collect(toUnmodifiableList());
By using this method, we get a List implementation that doesn’t support null values from Java’s ImmutableCollections:
class java.util.ImmutableCollections$ListN
The collectingAndThen method from Java’s Collectors class accepts a Collector and a finisher Function. This finisher is applied to the result returned from the Collector:
List<String> givenList = Arrays.asList("a", "b", "c");
List<String> result = givenList.stream()
.collect(collectingAndThen(toList(), ImmutableList::copyOf));
System.out.println(result.getClass());
With this approach, since we can’t use the toCollection Collector directly, we need to collect elements into a temporary list. Then, we construct an immutable list from it.
Java 16 introduces a new method on Stream API called toList(). This handy method returns an unmodifiable List containing the stream elements:
@Test
public void whenUsingStreamToList_thenReturnImmutableList() {
List<String> immutableList = Stream.of("a", "b", "c", "d").toList();
Assertions.assertThrows(UnsupportedOperationException.class, () -> {
immutableList.add("e");
});
}
As we can see in the unit test, Stream.toList() returns an immutable list. So, trying to add a new element to the list will simply lead to UnsupportedOperationException.
Please bear in mind that the new Stream.toList() method is slightly different from the existing Collectors.toList() as it returns an unmodifiable list.
We also have the option to implement a custom Collector.
To achieve this, we can use the static Collector.of method:
public static <T> Collector<T, List<T>, List<T>> toImmutableList() {
return Collector.of(ArrayList::new, List::add,
(left, right) -> {
left.addAll(right);
return left;
}, Collections::unmodifiableList);
}
We can use this function just like any built-in Collector:
List<String> givenList = Arrays.asList("a", "b", "c", "d");
List<String> result = givenList.stream()
.collect(MyImmutableListCollector.toImmutableList());
Finally, let’s check the output type:
class java.util.Collections$UnmodifiableRandomAccessList
Our implementation has one limitation – it always returns an immutable instance backed by an ArrayList. However, with a slight improvement, we can make this collector return a user-specified type:
public static <T, A extends List<T>> Collector<T, A, List<T>> toImmutableList(
Supplier<A> supplier) {
return Collector.of(
supplier,
List::add, (left, right) -> {
left.addAll(right);
return left;
}, Collections::unmodifiableList);
}
So now, instead of determining the Supplier in the method implementation, we’re requesting the Supplier from the user:
List<String> givenList = Arrays.asList("a", "b", "c", "d");
List<String> result = givenList.stream()
.collect(MyImmutableListCollector.toImmutableList(LinkedList::new));
Also, we’re using the LinkedList instead of ArrayList.
class java.util.Collections$UnmodifiableList
This time, we got UnmodifiableList instead of UnmodifiableRandomAccessList.
In this section, we’re going to use the Google Guava library to drive some of our examples:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
Starting with Guava 21, every immutable class comes with an accompanying Collector that’s as easy to use as Java’s standard Collectors:
List<Integer> list = IntStream.range(0, 9)
.boxed()
.collect(ImmutableList.toImmutableList());
The resulting instance is the RegularImmutableList:
class com.google.common.collect.RegularImmutableList
In this short article, we’ve seen various ways to collect a Stream into an immutable Collection.