Why does findFirst() throw a NullPointerException if the first element it finds is null?

JavaJava 8Java StreamOptional

Java Problem Overview


Why does this throw a java.lang.NullPointerException?

List<String> strings = new ArrayList<>();
strings.add(null);
strings.add("test");

String firstString = strings.stream()
		.findFirst()      // Exception thrown here
        .orElse("StringWhenListIsEmpty");
		//.orElse(null);  // Changing the `orElse()` to avoid ambiguity

The first item in strings is null, which is a perfectly acceptable value. Furthermore, findFirst() returns an Optional, which makes even more sense for findFirst() to be able to handle nulls.

EDIT: updated the orElse() to be less ambiguous.

Java Solutions


Solution 1 - Java

The reason for this is the use of Optional<T> in the return. Optional is not allowed to contain null. Essentially, it offers no way of distinguishing situations "it's not there" and "it's there, but it is set to null".

That's why the documentation explicitly prohibits the situation when null is selected in findFirst():

> Throws: > > NullPointerException - if the element selected is null

Solution 2 - Java

As already discussed, the API designers do not assume that the developer wants to treat null values and absent values the same way.

If you still want to do that, you may do it explicitly by applying the sequence

.map(Optional::ofNullable).findFirst().flatMap(Function.identity())

to the stream. The result will be an empty optional in both cases, if there is no first element or if the first element is null. So in your case, you may use

String firstString = strings.stream()
    .map(Optional::ofNullable).findFirst().flatMap(Function.identity())
    .orElse(null);

to get a null value if the first element is either absent or null.

If you want to distinguish between these cases, you may simply omit the flatMap step:

Optional<String> firstString = strings.stream()
    .map(Optional::ofNullable).findFirst().orElse(null);
System.out.println(firstString==null? "no such element":
                   firstString.orElse("first element is null"));

This is not much different to your updated question. You just have to replace "no such element" with "StringWhenListIsEmpty" and "first element is null" with null. But if you don’t like conditionals, you can achieve it also like:

String firstString = strings.stream().skip(0)
    .map(Optional::ofNullable).findFirst()
    .orElseGet(()->Optional.of("StringWhenListIsEmpty"))
    .orElse(null);

Now, firstString will be null if an element exists but is null and it will be "StringWhenListIsEmpty" when no element exists.

Solution 3 - Java

You can use java.util.Objects.nonNull to filter the list before find

something like

list.stream().filter(Objects::nonNull).findFirst();

Solution 4 - Java

The following code replaces findFirst() with limit(1) and replaces orElse() with reduce():

String firstString = strings.
   stream().
   limit(1).
   reduce("StringWhenListIsEmpty", (first, second) -> second);

limit() allows only 1 element to reach reduce. The BinaryOperator passed to reduce returns that 1 element or else "StringWhenListIsEmpty" if no elements reach the reduce.

The beauty of this solution is that Optional isn't allocated and the BinaryOperator lambda isn't going to allocate anything.

Solution 5 - Java

Optional is supposed to be a "value" type. (read the fine print in javadoc:) JVM could even replace all Optional<Foo> with just Foo, removing all boxing and unboxing costs. A null Foo means an empty Optional<Foo>.

It is a possible design to allow Optional with null value, without adding a boolean flag - just add a sentinel object. (could even use this as sentinel; see Throwable.cause)

The decision that Optional cannot wrap null is not based on runtime cost. This was a hugely contended issue and you need to dig the mailing lists. The decision is not convincing to everybody.

In any case, since Optional cannot wrap null value, it pushes us in a corner in cases like findFirst. They must have reasoned that null values are very rare (it was even considered that Stream should bar null values), therefore it is more convenient to throw exception on null values instead of on empty streams.

A workaround is to box null, e.g.

class Box<T>
    static Box<T> of(T value){ .. }

Optional<Box<String>> first = stream.map(Box::of).findFirst();

(They say the solution to every OOP problem is to introduce another type :)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionneverendingqsView Question on Stackoverflow
Solution 1 - JavaSergey KalinichenkoView Answer on Stackoverflow
Solution 2 - JavaHolgerView Answer on Stackoverflow
Solution 3 - JavaMattosView Answer on Stackoverflow
Solution 4 - JavaNathanView Answer on Stackoverflow
Solution 5 - JavaZhongYuView Answer on Stackoverflow