How to understand NullPointerException

    This simple article is more likely for beginning Java developers, although I often see experienced colleagues who look helplessly at a stack trace reporting a NullPointerException (NPE for short) and cannot draw any conclusions without a debugger. Of course, it’s better not to bring your application to NPE: null annotations, validation of input parameters and other methods will help you. But when the patient is already sick, it is necessary to treat him, and not drip on the brain that he walked in winter without a hat.

    So, you found out that your application crashed with NPE, and you only have stack trace. Perhaps you were sent by his client, or you yourself saw him in the logs. Let's see what conclusions can be drawn from it.

    NPE can occur in three cases:
    1. He was thrown using throw
    2. Someone threw null with throw
    3. Someone is trying to access a null link

    In the second and third cases, message in the exception object is always null, in the first it can be arbitrary. For example, java.lang.System.setProperty throws an NPE with the message "key can't be null" if you passed as key null. If you check each input parameter of your methods in the same way and throw an exception with a clear message, then you will not need the rest of this article.

    Null-reference can occur in the following cases:
    1. Calling a non-static class method
    2. Access (read or write) to a non-static field
    3. Access (read or write) to an array element
    4. Reading length of an array
    5. Implicit invocation of the valueOf method during unboxing

    It is important to understand that these cases should occur exactly in the line where the stack trace ends, and not elsewhere.

    Consider the following code:
     1: class Data {
     2:    private String val;
     3:    public Data(String val) {this.val = val;}
     4:    public String getValue() {return val;}
     5: }
     6:
     7: class Formatter {
     8:    public static String format(String value) {
     9:        return value.trim();
    10:    }
    11: }
    12:
    13: public class TestNPE {
    14:    public static String handle(Formatter f, Data d) {
    15:        return f.format(d.getValue());
    16:    }
    17: }

    The handle method with some parameters was called from somewhere, and you got:
    Exception in thread "main" java.lang.NullPointerException
        at TestNPE.handle(TestNPE.java:15)

    What is the reason for the exception - in f, d or d.val? It is easy to see that f in this line is not read at all, since the format method is static. Of course, accessing the static method through an instance of the class is bad, but such code is encountered (it could, for example, appear after refactoring). One way or another, the value of f cannot be the cause of the exception. If d were not null, but d.val was null, then an exception would have already occurred inside the format method (in the ninth line). Similarly, the problem could not be inside the getValue method, even if it were more complicated. Once the exception is in the fifteenth line, there is one possible reason: null in the d parameter.

    Here is another example:
     1: class Formatter {
     2:     public String format(String value) {
     3:         return "["+value+"]";
     4:     }
     5: }
     6: 
     7: public class TestNPE {
     8:     public static String handle(Formatter f, String s) {
     9:         if(s.isEmpty()) {
    10:             return "(none)";
    11:         }
    12:         return f.format(s.trim());
    13:     }
    14: }

    Call the handle method again and get
    Exception in thread "main" java.lang.NullPointerException
    	at TestNPE.handle(TestNPE.java:12)

    Now the format method is non-static, and f could very well be the source of the error. But s cannot be under any sauce: in the ninth line there was already an appeal to s. If s were null, an exception would have occurred on the ninth line. Viewing the logic of the code before the exception quite often helps to discard some options.

    With logic, of course, you have to be careful. Suppose a condition in line nine would be written like this:
    if("".equals(s))

    Now there is no call to fields and methods s in the line itself, and the equals method correctly processes null, returning false, so in this case both f and s could cause an error in the twelfth line. When analyzing the parent code, specify in the documentation or source how the methods and constructions used respond to null. The string concatenation operator +, for example, never calls NPE.

    Here is the code (Java version may play a role here, I use Oracle JDK 1.7.0.45):
     1: import java.io.PrintWriter;
     2: 
     3: public class TestNPE {
     4:     public static void dump(PrintWriter pw, MyObject obj) {
     5:         pw.print(obj);
     6:     }
     7: }

    We call the dump method, we get the following exception:
    Exception in thread "main" java.lang.NullPointerException
    	at java.io.PrintWriter.write(PrintWriter.java:473)
    	at java.io.PrintWriter.print(PrintWriter.java:617)
    	at TestNPE.dump(TestNPE.java:5)

    The pw parameter cannot be null, otherwise we would not be able to enter the print method. Maybe null in obj? It is easy to verify that pw.print (null) prints the string “null” without any exceptions. Let's go from the end. An exception happened here:
    472: public void write(String s) {
    473:     write(s, 0, s.length());
    474: }

    Line 473 has only one possible reason for NPE: a call to the length method of line s. So s contains null. How could this happen? Go up the stack above:
    616: public void print(Object obj) {
    617:     write(String.valueOf(obj));
    618: }

    The result of calling the String.valueOf method is passed to the write method. In which case can it return null?
    public static String valueOf(Object obj) {
       return (obj == null) ? "null" : obj.toString();
    }

    The only possible option is obj is not null, but obj.toString () returned null. Therefore, the error must be sought in the overridden toString () method of our MyObject object. Note that MyObject did not appear on the stack trace at all, but the problem is there. Such a simple analysis can save a lot of time trying to reproduce the situation in the debugger.

    Do not forget about the insidious autoboxing. Let us have the following code:
     1: public class TestNPE {
     2:     public static int getCount(MyContainer obj) {
     3:         return obj.getCount();
     4:     }
     5: }
    

    And such an exception:
    Exception in thread "main" java.lang.NullPointerException
    	at TestNPE.getCount(TestNPE.java:3)

    At first glance, the only option is null in the obj parameter. But you should take a look at the MyContainer class:
    import java.util.List;
    public class MyContainer {
        List elements;
        public MyContainer(List elements) {
            this.elements = elements;
        }
        public Integer getCount() {
            return elements == null ? null : elements.size();
        }
    }

    We see that getCount () returns an Integer, which automatically turns into an int in the third line of TestNPE.java, which means that if getCount () returns null, this is exactly the exception that we see. If you find a class similar to the MyContainer class, look in the history of the version control system who the author is and sprinkle crumbs under the covers.

    Remember that if a method accepts an int parameter, and you pass Integer null, then anboxing will happen before the method is called, so the NPE will point to the line with the call.

    In conclusion, I would like to wish to run the debugger less often: after some training, the analysis of the code in the head is often faster than the reproduction of an elusive situation.

    Also popular now: