-4
import java.util.function.Function;

public class Playground {
    public static void main (String[] args) {
        Object o = null;
        System.out.println(o);
        Function<Object, String> toStringFunc = Object::toString;
        String s = toStringFunc.apply(o);
        System.out.println(s);
    }
}

This code will result in a NullPointerException being thrown, reported at the line containing toStringFunc.apply(o).

This is a trivial example, so it's easy to see that o == null, but how in general can we understand why this line of code would throw a NPE, as toStringFunc, the only variable being dereferenced in that line, is not null.

Holger
  • 285,553
  • 42
  • 434
  • 765
mpsrig
  • 23
  • 2
  • https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it – Russiancold Oct 23 '17 at 20:44
  • 6
    Null has not any methods. If you try to call 'o.toString()' will throw the NPE too. – akop Oct 23 '17 at 20:46
  • 2
    I don't understand the question. You know that a NPE is thrown because `o == null` but you are also asking why a NPE is thrown. – Paul Boddington Oct 23 '17 at 20:47
  • 6
    The `Object::toString` method reference is equivalent to lambda `t -> t.toString()` and anonymous class `new Function<>() { public String apply(Object t) { return t.toString(); } }` --- When you call `apply(null)`, the `toString()` call causes NPE. – Andreas Oct 23 '17 at 20:47
  • @Andreas: Perhaps the confusion lies in the fact that newer JREs omit stack trace entries of certain synthetic methods, hence the code actually trying to invoke the `toString()` method is not in the stack trace. In this regard, lambda expressions and method references differ as in the lambda case, the synthetic method holding the lambda’s body is still in the trace. This has been already discussed in [this Q&A](https://stackoverflow.com/q/39862244/2711488). – Holger Oct 24 '17 at 13:56
  • You can run your application with `-XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames` to see the synthetic method in the stack trace. This won’t tell you much about its code, but at least seeing a synthetic method, you will realize that this is likely a method reference and hence, either the function’s first parameter has been `null` or an attempt to unbox `null` has been made. – Holger Oct 24 '17 at 14:04

2 Answers2

2

Normally, you would look at the deepest stack trace entry to find out which variable has been dereferenced in the corresponding line. You are right in that this is not possible here when the stack trace looks like

Exception in thread "main" java.lang.NullPointerException
        at Playground.main(Playground.java:9)

The problem is that in this line in the main method, the actual dereferencing did not happen. It happens within the invoked apply method whose implementation is part of a JRE generated class and whose stack frame has been omitted from the trace.

This was not always the case. It’s the result of JDK-8025636: Hide lambda proxy frames in stacktraces. This change has been discussed in this Q&A as well.

The hiding works smoothly for lambda expressions, e.g. if you used

import java.util.function.Function;

public class Playground {
    public static void main (String[] args) {
        Object o = null;
        System.out.println(o);
        Function<Object, String> toStringFunc = obj -> obj.toString();
        String s = toStringFunc.apply(o);
        System.out.println(s);
    }
}

instead, the stack trace looked like

Exception in thread "main" java.lang.NullPointerException
        at Playground.lambda$main$0(Playground.java:8)
        at Playground.main(Playground.java:9)

showing the exact place where the dereferencing happened while the irrelevant generated method mediating between the caller (main) and the callee (lambda$main$0) has been omitted.

Unfortunately, this doesn’t work that smooth for method references where the target method is invoked directly without the aid of another visible method. This backfires especially in cases where the target method is not in the trace as the invocation itself failed, e.g. when the receiver instance is null. Similar problems may occur when an attempt to unbox null happened in the generated code before or after the invocation of the target method.

One solution is to run the JVM with the options
-XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames to disable the hiding of stack frames. This may cause much longer stack traces as it also affects other binding code, e.g. for Reflection. So you may only use this option when you have the suspicion that a certain exception did not happen at the reported place, but at a hidden frame. Using this option with you original code yields:

Exception in thread "main" java.lang.NullPointerException
        at Playground$$Lambda$1/321001045.apply(<Unknown>:1000001)
        at Playground.main(Playground.java:9)

The name of the class and method may vary, but it’s recognizable as generated code. From this stack trace, you can conclude that not a variable being dereferenced at main, line 9, but rather one of the arguments passed to the invocation must have been null.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Is the dereferencing also the reason why the `apply` method doesn't have the `@throws NullPointerException` compared to the other default methods in the interface? Or is it just not mentioned because it's an abstract method and doesn't have a default implementation and the NPE is just a runtime exception? – Nick Vanderhoven Oct 24 '17 at 19:53
  • 1
    @NickVanderhoven: The interface method `apply` does not mandate that `null` is not allowed. It’s an implementation specific behavior, e.g. when implementing the `Function` as `o -> o.toString()` resp. `Object::toString`. But you could also implement it as, e.g. `String::valueOf`, creating a `Function` that allows `null` arguments. – Holger Oct 24 '17 at 20:53
  • That's a logic explanation and should have been my first line of thoughts, I missed that for some reason – Nick Vanderhoven Oct 24 '17 at 21:04
  • This is a great answer - thank you for unpacking the meaning in my (admittedly vague) question! – mpsrig Oct 25 '17 at 18:33
0

when you do

toStringFunc.apply(o);

this is the same that

o.toString()

that's why you get a NullPointerException so, you must ensure that your Object is not null.

Pshemo
  • 122,468
  • 25
  • 185
  • 269