Introduction

I have written about Java stream methods extensively in the past. In this post, we will see six ways to convert a Java stream to a List. You can easily extend these if you want to convert a stream to a Set as well.

1 - Collectors.toList

The first method is the most obvious and the easiest one. We use the Collectors class static method toList() to get a collector and pass it to the collect method on the stream. This collector accumulates the stream elements into a new List.

There are no guarantees on the type, the mutability, serializability or thread-safety of the list that is returned. It is an implementation detail that it returns an ArrayList today. It could change in the future.

Stream<String> stream = Stream.of("Jon", "Adam", "Perry", "Monty");
List<String> names = stream
        .collect(Collectors.toList());
System.out.println(names);

Prints,

[Jon, Adam, Perry, Monty]

2 - Collectors.toCollection

If we wanted more control over the type of list we collect the stream elements into, we can use Collectors.toCollection method. This method accepts a Supplier of a Collection type as an argument. The returned collector accumulates the stream elements into the collection returned by the supplier (that we pass).

For example, if we wanted the result list to be an ArrayList, we can pass a supplier as shown below

Stream<String> stream = Stream.of("Jon", "Adam", "Perry", "Monty");
List<String> names = stream
        .collect(Collectors.toCollection(() -> new ArrayList()));
System.out.println(names);

Using method reference, we can rewrite the above as

names = stream
        .collect(Collectors.toCollection(ArrayList::new));

Similarly, if we wanted the result to be a LinkedList, we just change the supplier factory to create a new LinkedList instead. Hence, the result will be a LinkedList.

Stream<String> stream = Stream.of("Jon", "Adam", "Perry", "Monty");
List<String> names = stream
        .collect(Collectors.toCollection(LinkedList::new));
System.out.println(names);

3 - Collectors.toUnmodifiableList

The Collectors class has a static method called toUnmodifiableList which returns a Collector that accumulates the stream elements into an unmodifiable list as per the encounter order.

Stream<String> stream = Stream.of("Jon", "Adam", "Perry", "Monty");
List<String> names = stream
        .collect(Collectors.toUnmodifiableList());
System.out.println(names);

null values not allowed

Unlike the other Collector methods we have seen so far, toUnmodifiableList does not allow null values in the stream. It will throw a NullPointerException if there are null values in the stream.

4 - forEach

In this rudimentary approach, we use forEach to iterate over the stream elements and add it to the result list. Hence, we pass the action to add to the list as a consumer.

There are some problems when dealing with parallel stream pipelines.

  1. There is no guarantee to preserve the encounter order of the stream. It could thus add to the list in any order.
  2. The result list we add to may behave in weird ways when added from multiple threads in a parallel stream.
Stream<String> stream = Stream.of("Jon", "Adam", "Perry", "Monty");

List<String> names = new ArrayList<>();
stream.forEach(names::add);
System.out.println(names);

ArrayList is not thread-safe but we are adding to it from multiple threads in the above code. Hence, we have to use a collection object that is thread-safe.

5 - forEachOrdered

To overcome the problems in the previous method, we could use the forEachOrdered method.

This method obeys the encounter order of a stream even for parallel streams (thus it can affect performance). As per Javadoc,

Performing the action for one element happens-before performing the action for subsequent elements, but for any given element, the action may be performed in whatever thread the library chooses.

Stream<String> stream = Stream.of("Jon", "Adam", "Perry", "Monty");

List<String> names = new ArrayList<>();
stream.forEachOrdered(names::add);
System.out.println(names);

6 - Convert to an Array and then to List

In this option, we first convert the stream to an array through the toArray method. The toArray method accepts a generator function to allocate the result array.

Stream<String> stream = Stream.of("Jon", "Adam", "Perry", "Monty");
List<String> names = Arrays.asList(stream
        .toArray(String[]::new));
System.out.println(names);

The above shown generator function can also be written as size -> new String[size]. It takes an integer and allocates the result array of that size.

Then, we call Arrays.asList method passing the array to get back a List. The Arrays#asList method acts as a bridge between array-based and collection-based APIs.

It returns a list which is backed up by the passed array. Hence, changes made to the array will be visible in the list and vice-versa. Since it does not copy the array elements into a new list, the only additional thing is the call to asList.

Mutability of the returned list by Arrays.asList

The returned list is modifiable (supports set method). But it does not support adding to the list.

Any changes to the array will be reflected in the list. But the array created from the stream was an intermediate object. No references to it is available after wrapping it in Arrays.asList in the above example.

Summary

Options 1, 2 and 3 are the recommended ways to convert a Java stream to a List. Options 4 and 5 (forEach and forEachOrdered) are not recommended due to the discussed limitations. Option 6 (converting a stream to array and then to a list) is a roundabout way.

Generic method to convert stream to a List

You can make a generic method to convert a stream to a List.

Example: Option 1 can be written as a generic method as:

private static <T> List<T> collectorsToList(Stream<T> stream) {
    return stream
            .collect(Collectors.toList());
}

In the same way you can turn any of the approaches we have seen into a generic method.

Wherever you want to convert a stream to a list, you can call the collectorsToList helper method passing the list. However, this logic is not that huge that warrants it to be an utility method.

Conclusion

In this post, we saw six ways to convert a Java Stream to a List. I recommend checking out the other posts on the Java stream.

References

Why is list.parallelStream().forEach() not processing all the elements in the list in Java