Wednesday, June 8, 2016

How do I track down the cause of the NPE exception in my code?

This is the hard part. The short answer is to apply logical inference to the evidence provided by the stack trace, the source code and the relevant API documentation.
Lets illustrate with the simple example (above) first. We start by looking at the line that the stacktrace has told us is where the NPE happened:
            int length = foo.length(); // HERE
How can that throw an NPE?
In fact there is only one way: it can only happen if foo has the value null. We then try to run the length() method on null and .... BANG!
But (I hear you say) what if the NPE was thrown inside the length() method call?
Well if that happened, the stacktrace would look different. The first "at" line would say that the exception was thrown in some line in the java.lang.String class, and line 4 of Test.java would be the second "at" line.
So where did that null come from? In this case it is obvious and it is obvious what we need to do to fix it. (Assign a non-null value to foo)
OK, so lets try a slightly more tricky example. This will require some logical deduction.
public class Test {

    private static String[] foo = new String[2];

    private static int test(String[] bar, int pos) {
        return bar[pos].length();
    }

    public static void main(String[] args) {
        int length = test(foo, 1);
    }
}

$ javac Test.java 
$ java Test
Exception in thread "main" java.lang.NullPointerException
    at Test.test(Test.java:6)
    at Test.main(Test.java:10)
$ 
So now we have 2 "at" lines. The first one is for this line:
    return args[pos].length();
and the second one is for this line:
    int length = test(foo, 1);
So looking at the first line, how could that throw an NPE? In fact, there are two ways:
  • If the value of bar is null then bar[pos] will throw an NPE.
  • If the value of bar[pos] is null then calling length() on it will throw an NPE.
So next we need to figure out which of those scenarios explains what is actually happening. Lets start by exploring the first one:
Where does bar come from? It is a parameter to the test method call, and if we look at how testwas called, we can see that it comes from the foo static variable. And we can see clearly that we initialized foo to a non-null value. That is sufficient to tentatively dismiss this explanation. (In theory, something else could change foo to null ... but that's not happening here.)
So what about our 2nd scenario? Well we can see that pos is 1, so that means that foo[1] must be null. Is that possible?
Indeed it is! And that is the problem. When we initialize like this:
private static String[] foo = new String[2];
we allocate a String[] with two elements that are initialized to null. And then we didn't change the contents of foo ... so foo[1] will still be null.

No comments:

Post a Comment