📜 ⬆️ ⬇️

How to understand NullPointerException

This simple article is more likely to beginner Java developers, although I often see experienced colleagues who look helplessly at the stack trace reporting 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 to drip on the brains that he went in the winter without a hat.

So, you learned that your application fell with NPE, and you only have the stack trace. Perhaps a client sent it to you, or you yourself saw it in the logs. Let's see what conclusions can be made from it.

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

In the second and third cases, message in the exception object is always null, in the first case it can be arbitrary. For example, java.lang.System.setProperty throws NPE with the message “key can't be null”, if you passed the 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.

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

It is important to understand that these cases should occur exactly in the line on which the stack trace ends, and not elsewhere.
')
Consider this 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 readable at all, since the format method is static. Of course, accessing a static method through an instance of a 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 was not null, but d.val was null, then the exception would have occurred already 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 may 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, the exception would have happened in the ninth line. Viewing the code logic before the exception quite often helps to discard some options.

With logic, of course, one must be attentive. Suppose the condition in the ninth line would be written like this:
 if("".equals(s)) 

Now in the very line of accessing the fields and methods, s is not present, and the equals method correctly processes null, returning false, so in this case, an error in the twelfth line could cause both f and s. Analyzing the superior code, specify in the documentation or source code how the methods and constructions used react to null. The string concatenation operator +, for example, never calls NPE.

Here is the code (Java version can play a role, 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: } 

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. Perhaps null in obj? It is easy to check that pw.print (null) prints the string "null" without any exceptions. Come from the end. An exception happened here:
 472: public void write(String s) { 473: write(s, 0, s.length()); 474: } 

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

The write method returns the result of calling the String.valueOf 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 () returns null. This means that the error should be looked up in the overridden toString () method of our MyObject object. Notice that in the trace trace MyObject did not appear 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 treacherous autoboxing. Let us have this 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<String> elements; public MyContainer(List<String> 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 exactly in the third line of TestNPE.java, which means that if getCount () returns null, exactly the exception that we see will occur. Having discovered a class similar to the MyContainer class, look in the history of the version control system who its author is and place crumbs under it for him.

Remember that if a method accepts an int parameter, and you pass Integer null, then anboxing will happen before the method is called, so 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 performed faster than the reproduction of an elusive situation.

Source: https://habr.com/ru/post/221243/


All Articles