Can Java's Stream.collect() return null?

Collector.toList() will return an empty List for you.

Here is the implementation:

public static <T>
Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_ID);
}

As you can see ArrayList::new is being used as a container for your items.

From JavaDoc of Collector:

A mutable reduction operation that accumulates input elements into a mutable result container, optionally transforming the accumulated result into a final representation after all input elements have been processed. Reduction operations can be performed either sequentially or in parallel.

A Collector is specified by four functions that work together to accumulate entries into a mutable result container, and optionally perform a final transform on the result. They are:

  • creation of a new result container (supplier())

  • incorporating a new data element into a result container (accumulator())

  • combining two result containers into one (combiner())
  • performing an optional final transform on the container (finisher())

And

A sequential implementation of a reduction using a collector would create a single result container using the supplier function, and invoke the accumulator function once for each input element. A parallel implementation would partition the input, create a result container for each partition, accumulate the contents of each partition into a subresult for that partition, and then use the combiner function to merge the subresults into a combined result.

So as long as you don't do weird things like combine function return null, the Collector always return at least a mutable container using your provided supplier function.

And I think it's very counter-intuitive if an implementation would ever return null container.


This is not dependent on Stream.collect, but on the individual Collector. Collectors.toList() will return an empty ArrayList.

That said, there's no reason someone couldn't use a weird Collector to return null in certain circumstances:

.collect(
    Collector.of(
        ArrayList::new,
        ArrayList::add,
        (a, b) -> {
            a.addAll(b);
            return a;
        },
        a -> a.isEmpty() ? null : a  // finisher replaces empty list with null
    )
);

So the Collector is the thing you need to remember to check. I believe all of the Collectors available out-of-the-box will return empty collections, as you'd expect.


I think this part of the documentation says that it cannot be null:

Returns a Collector that accumulates the input elements into a new List.

Highlights added by me. I think this new List means that something that isn't null.

I started to check ReferencePipeline.collect() to check whether it's true for the actual implementation. Unfortunately, it was a futile attempt. There are so many cases here, like is it parallel? is it after a forEach? etc.