56

The JavaDoc for Stream.collect() says that it returns "the result of the reduction". That doesn't tell me if code like this can return null for filteredList:

List<String> filteredList = inputList.stream()
    .filter(c -> c.isActive())
    .collect(Collectors.toList());

I would expect that if it could return null then it would return an Optional, but it doesn't say that either.

Is it documented anywhere whether Stream.collect() can return null?

2
  • 4
    No this will return you an empty list even if nothing gets filtered Commented May 9, 2018 at 15:14
  • 1
    I tested all Collectors returned by java.util.stream.Collectors.toXXX and they will all return an empty collection (not null) if provided an empty stream. Commented Jun 30, 2023 at 14:26

7 Answers 7

73

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.

28

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.

2
  • I believe this is the correct answer, on the phone now, but is there really no specification about this? I would doubt it...
    – Eugene
    Commented May 9, 2018 at 19:24
  • 1
    agree with Eugene, this should be the correct answer Commented Dec 13, 2019 at 17:53
14

You could use Collectors::collectingAndThen to pass collect() result to a Function<T,R>. Return value of the Function<T,R> will be return value of collect().

List<String> filteredList = inputList.stream()
 .filter(c -> c.isActive())
 .collect(Collectors.collectingAndThen(Collectors.toList(), c -> !c.isEmpty()?c:null));
4

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.

2
  • very thin IMO... If that is indeed what the documentation means, than there is a problem with it not being explicit enough
    – Eugene
    Commented May 9, 2018 at 19:42
  • In my opinion, javadoc should be presize and to the point. It clearly says what you can expect from the method which is a "new List". It should not mention other values which this method "does not" return.
    – Mrinal
    Commented May 10, 2018 at 7:32
1

This is collector-dependant. The one You're using (Collectors.toList()) returns an empty list.

1

I believe the following code is a good implementation if you really need to return null when the list is empty (assuming the type of the variable c is MyObj):

import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;

...

  List<String> filteredList = inputList.stream()
    .filter(MyObj::isActive)
    .collect(collectingAndThen(toList(), Stream::of)
    .filter(List::isEmpty)
    .findAny()
    .orElse(null);
0

No collect will never return null, in order to check use isEmpty() instead of null

Not the answer you're looking for? Browse other questions tagged or ask your own question.