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

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


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.


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

something like

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

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.