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

6

123

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.

Multiplicity answered 8/9, 2015 at 20:36 Comment(5)
null isn't perfectly acceptable value... use "" insteadReginiaregiomontanus
@MicheleLacorte although I'm using String here, what if it's a list that represents a column in the DB? The value of the first row for that column can be null.Multiplicity
Yes but in java null isn't acceptable..use query to set null into dbReginiaregiomontanus
@MicheleLacorte, null is a perfectly acceptable value in Java, generally speaking. In particular, it is a valid element for an ArrayList<String>. Like any other value, however, there are limitations on what can be done with it. "Don't ever use null" is not useful advice, as you cannot avoid it.Plate
@NathanHughes - I suspect that by the time you call findFirst(), there's nothing else you want to do.Multiplicity
M
91

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

Magnetograph answered 8/9, 2015 at 20:40 Comment(6)
It would be simple to track if a value does exist with a private boolean inside instances of Optional. Anyway, I think I'm bordering ranting - if the language doesn't support it, it doesn't support it.Multiplicity
Also, I would be happy if you have any alternatives (was hoping for a functional programming-like solution). Time to convert it to a for loop.Multiplicity
@Multiplicity Absolutely, using a boolean to differentiate these two situations would make perfect sense. To me, it looks like the use of Optional<T> here was a questionable choice.Magnetograph
@Multiplicity I can't think of any nice-looking alternative to this, besides rolling your own null, which isn't ideal either.Magnetograph
I ended up writing a private method that gets the iterator from any Iterable type, checks hasNext(), and returns the appropriate value.Multiplicity
I think it makes more sense if findFirst returns an empty Optional value in PO's caseShiah
M
76

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()
    .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.

Moue answered 9/9, 2015 at 10:3 Comment(9)
Sorry I realized that my question might have implied I wanted to return null for either 1) the first element is null or 2) no elements exist inside the list. I have updated the question to remove the ambiguity.Multiplicity
In the 3rd code snippet, an Optional may be assigned to null. Since Optional is supposed to be a "value type", it should never be null. And an Optional should never be compared by ==. The code may fail in Java 10 :) or when-ever value type is introduced to Java.Eyebrow
@bayou.io: the documentation doesn’t say that references to value types can’t be null and while instances should never be compared by ==, the reference may be tested for null using == as that’s the only way to test it for null. I can’t see how such a transition to “never null” should work for existing code as even the default value for all instance variables and array elements is null. The snippet surely is not the best code but neither is the task of treating nulls as present values.Moue
see john rose - nor can it be compared with the “==” operator, not even with a nullEyebrow
anyways, this is their plan, that Optional will not be used like "normal" reference types. so that in future, it can be transitioned to "value type". But people will definitely "misuse" it; the warning on javadoc is certainly very inadequate.Eyebrow
Since this code is using a Generic API, this is what the concept calls a boxed representation which can be null. However, since such a hypothetical language change would cause the compiler to issue an error here (not break the code silently), I can live with the fact, that it would possibly have to be adapted for Java 10. I suppose, the Stream API will look quite different then as well…Moue
Add 3rd code snippet, I see Optional.empty as a result of .stream().map(Optional::ofNullable).findFirst().orElse(null) (not null) - in case the first element in stream is null.Fussy
What's the purpose of skip(0) here?Chilson
@RayJasson good question. I suppose, it’s a leftover from some testing that slipped through. Should have no effect. I removed it.Moue
T
41

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

something like

list.stream().filter(Objects::nonNull).findFirst();
Taveras answered 18/10, 2017 at 19:21 Comment(2)
I want firstString to be null if the first item in strings is null.Multiplicity
unfortunately it uses Optional.of which is not null safe. You could map to Optional.ofNullable and then use findFirst but you will end up with an Optional of OptionalTaveras
M
19

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.

Mustachio answered 23/1, 2017 at 16:25 Comment(0)
E
1

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 :)

Eyebrow answered 8/9, 2015 at 22:21 Comment(2)
There is no need to create another Box type. The Optional type itself can serve this purpose. See my answer for an example.Moue
@Moue - yes, but that might be confusing since it's not the intended purpose of Optional. In OP's case, null is a valid value like any other, no special treatment for it. (until sometime later:)Eyebrow
C
0

First,Java's stream operations such as map and filter do not actually execute until a terminal operation such as findFirst() is called. When findFirst() is called, the stream framework starts executing the entire chain of operations.

So, It can be find in the source code. This is the core method: enter image description here,the function is mentioned in annotation. Next,it will step into enter image description here in FindOps.

wrapAndCopyInto(S sink, Spliterator<p_in> spliterator) Applies the pipeline stage described by the current PipelineHelper to the supplied Spliterator and sends the result to the supplied Sink

Then it will start to process the stream. enter image description here This is used to push the elements obtained from the Spliterator into the provided receiver Sink. performs a Sink#cancelationRequestedl) after each element and terminates if the return request is true. This method, when implemented, adheres to the Sink protocol, i.e., Sink#begin->Sink#accept->Sink->end. Now,we can see the accept method in FindSink enter image description here If the value is null, hasValue will be true,but this.value will be null

Now we can return to evaluateSequential,we will get a Sink,which hasValue = true and value = null. Then when you call the get method, it will call Optional.of(value). enter image description here Optional cant accept a null value,so it will throw a NPE

Cavorelievo answered 31/1, 2024 at 13:51 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Heideheidegger

© 2022 - 2025 — McMap. All rights reserved.