JDK-6717558 : NullPointerException's from JVM lack any detail message
  • Type: Enhancement
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 5.0
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: linux
  • CPU: x86
  • Submitted: 2008-06-20
  • Updated: 2020-01-27
  • Resolved: 2010-08-27
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Description
Consider

---%<---
import java.util.Collections;
import java.util.Map;
public class UnhelpfulNPEs {
    public static void main(String[] args) {
        try {
            System.err.println(UnhelpfulNPEs.class.getProtectionDomain().getCodeSource().getCodeSigners()[0]); // NPE
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            Map<String,Integer> numbers = Collections.singletonMap("X", 5);
            int x = numbers.get("x"); // NPE
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
---%<---

producing just

---%<---
java.lang.NullPointerException
        at UnhelpfulNPEs.main(UnhelpfulNPEs.java:6)
java.lang.NullPointerException
        at UnhelpfulNPEs.main(UnhelpfulNPEs.java:12)
---%<---

For the first case it is not obvious which method actually returned null. -g:lines does not help you much here, especially if you have a complex, multiline expression in a single statement. In a realistic program it is not so unusual for there to be multiple methods in the statement which might return null under some conceivable conditions. (It is especially hard to know this in the absence of a standard @NotNull annotation.)

For the second case, most Java developers will stare long and hard at the expression trying to imagine how Collections.singletonMap could return null, thus leaving 'numbers' null - the only apparent member access in the statement. Of course the problem is not the call to numbers.get("x") at all, but the resulting auto-unboxing.

In any program with long, complicated methods, it is quite common to receive bug reports from the field showing a NullPointerException. Usually you can figure out what the proximate cause of the NPE was from the line number in the stack traces, except

1. in the cases above

2. when the code was compiled without debugging information

3. when there have been changes in the class since the last public release and you are not sure which exact version of the class the end user was running

Sometimes developers even fix "the NPE" that was apparently reported, when in fact the user encountered a different nearby NPE.

What is frustrating is that the JVM knows some useful things about the cause of the exception but is refusing to tell you in the detail message.

Comments
Someone raised this very issue in, if I recall correctly, the Hotspot BOF at JavaOne. Initially the issue was misunderstood, but I later had a discussion with Karen Kinnear about it. There should certainly be some additional information available when the NPE is detected, but exactly what will depend on context. When running in the interpreter there should be information about the bytecode being executed, but what you probably want is the previous operation. For example, in a chain: a().b().c(); there will be a series of "invoke" bytecodes, but if b() returns null then it will be "invoke c" that fails. However it may not be obvious how to track where the null came from in general. In compiled code NPE is detected through SIGSEGV and there is even less context to use to provide useful information. [edited to fix typo]
28-01-2015

SUGGESTED FIX All JVM code which constructs a NullPointerException should be reviewed for the possibility of adding an informative detail message. In particular, for the code sample I would like to see something like java.lang.NullPointerException: javax.security.CodeSigner[index] at UnhelpfulNPEs.main(UnhelpfulNPEs.java:6) java.lang.NullPointerException: Integer -> int unboxing at UnhelpfulNPEs.main(UnhelpfulNPEs.java:12) Construction of such messages does not require knowledge of anything about the execution context beyond the bytecode operation being run: the static type of the operand, and the name of the member or operation. Other example messages should follow naturally from the JVM spec, e.g.: java.lang.NullPointerException: javax.security.ProtectionDomain.getCodeSource() java.lang.NullPointerException: java.util.Map.put(java.lang.String, java.lang.String) java.lang.NullPointerException: java.awt.Dimension.width java.lang.NullPointerException: java.lang.String[].length java.lang.NullPointerException: throw java.io.IOException
20-06-2008