NullPointerException in Java: Causes and Ways to Avoid It
This article explains what NullPointerException is with its common causes and best practices you can do to avoid NPE. Read on to find out more!
Join the DZone community and get the full member experience.
Join For FreeNullPointerException: The Most Common Exception
NullPointerException (NPE) is the most common exception in Java. The cause of this exception is known but looks like that in most cases developers prefer to ignore it and don't take any action. I personally expect that reasons for such behavior are the following:
- Most of the developers don't see any problem here and recognize all NPE exceptions as the developer's fault.
- Developers who are aware of this design problem don't know how to fix it.
In this article, I'll explain what is the root of this problem and provide ways to fix that problem.
The Root of the Problem: Java Weak Type Safety
Have you heard of Compile Type Safety? If not in this article you can find out what it is and what is the difference between Compile-Time and Type Safety.
Java provides Compile Type Safety and it gives a guarantee to the developer that he can't mismatch different variables types. And, if you did - Java would let him know it even on the compilation step. In the example above we try to assign to String variable Integer value:
Java Type Safetiness Ruined by Null Reference
Java validates the type of the variable and the type of assigned value during compilation. What is the problem then? Well, the problem is the NULL value. Null values stand for all non initialized objects. And, as far as any object can be initialized then a Null Value can be assigned to any type.
So, Java allows next assignments:
What is wrong here? Objects are not initialized so they point to the Null Reference. It seems very natural but actually, it's the root of great evil.
NullPointerException: Consequence of Weak Type Safeties
As far as Java see no difference between Null and real object it leads to impossible operations like the next one below:
So, from a compiler perspective, there is nothing wrong. Null belongs to String type and Java will not even print a warning. Actually, you can compile even the next code:
But, once we run this program, it will fail with NullPointerException:
NullPointerException Definition
NullPointerException is a Runtime exception that is thrown when Java tries to call any method on a real object but in runtime this object references to the Null Reference. More details about exceptions and their nature you can find in this article.
Why Is NullPointerException the Most Popular Exception?
Developers are humans and use to make mistakes to forget and to haste. As a consequence, they miss to:
- Initialize objects
- Validate objects
There is no cure for human nature and there is nothing to do with it. What are the practical ways to avoid NPE? Let's review an example in the next chapter and try to fix it.
Explaining NullPointerException Using the User Address Example
In our example, we have a User object with an Address field. Potentially, both of them might be null. Let's see how we can avoid NullPointerException.
Avoiding NullPointerException Using Simple != Null Check
Now, let's prevent this problem by simple checking, not null checking:
Can we improve this solution? Yes, we can use Optional. Using map function we can write equivalent similar to the previous statement:
Does optional provide benefits compared to simple null check? Yes, it does. Optional reinsures us that data we use in ifPresent lambda is not null. But, what if the user or address is null? Then, ifPresent will be silently ignored.
And, even if we forget to use Optional features, the idea will highlight .get() reminding us to provide the design with a null check.
Optional Was Released in 2014. Why Is It Not That Popular?
The optional feature was released in Java 1.8 but it's not that widely used. There are a couple of reasons:
- It's very verbose and pollutes the code (Personally I think it's the main reason, Java itself is pretty wordy and with Optional it's become ridiculously big).
- It's unclear, behind all map/flatmap/ifpresent you can lose the point of the logic. So ugly null check is straightforward and obvious.
- Optional itself might lead developers to create more NPEs eg by using Optional.of(nullable).
So, for the mentioned reasons some teams prefer to use null checks. And in order to avoid any NPE exceptions, cover such logic with a bunch of tests.
Null Check and Optional Do They Solve The Problem? No.
Two "solutions" are shown above, are they really solutions? Null check together with Optional serving to the same purpose -- to provide validation for data that might be null. Additionally, Optional reminds the developer that returned value can be null. But, in general, the key problem is hidden in human nature -- to forget or miss potential null scenarios. We need a solution that will point to the developer what he missed on the compilation step.
@NotNull @Nullable Annotation Processors Help to Identify Potential Nulls.
We need a solution that can read our code on the compilation step and notify us that we missed a potential NPE scenario. For this purpose, we can use Java Annotation Processors. Java Annotation Processors have many purposes but can be used for our case as well. In this article, you can find an example of how to use an annotation processor in order to check mutability.
There are a couple of annotation processors related to NPE problems. Not all of them are the same and follow completely different approaches. In the next chapter, we review existent @NotNull and @Nullable annotations providers.
Lombok @NotNull Annotation
Lombok @NotNull Annotation is used to generate not null checks that can prevent execution but only in Runtime. So it doesn't fit our purpose. Shortly, this annotation does the next thing:
Checker Framework @NonNull @Nullable Annotations Processors
Checker Framework provides @NonNull and @Nullable annotations together with a compiler processor step that can identify potential null checks. The framework can find potential nulls by forcing developers to specify Nullability. So, whenever you return something you have to explicitly declare can returned result be Nullable or NotNullable... Let's take a look at the next example:
A simple method that might potentially return Null instead of String:
Now, let's use our Checker Framework and see if it would be happy to compile it:
No, it's not happy at all. It says that we return a potentially nullable string and that it isn't marked with @Nullable annotation. Now, let's mark it as @Nullable, and try to use it:
Will that framework find anything wrong in that code? Let's run compilation check again:
So, it finds a potential problem at line 19 where we try to call .length() on Nullable string. Let's fix it using Null check and Optional ifPresent:
And, after compilation, we get a successful build:
Checker Framework Limitations
So far Checker Framework shows good results and highlighted potential NPE. But, at what cost? Now we are obliged to mark all potentially Nullable methods by the @Nullable method. It seems it's a mandatory step, and we can't avoid it. But, it's not the only limitation. Let's create a simple class with two fields one of those we mark as @NonNull:
Will Checker Framework accept this code?
Checker Framework forces us to have a constructor that initializes id value, eg like this:
So, Framework did not just identify potential NPE, but also force us to follow specific requirements or designs. Actually, this requirement is crucial. And, it's questionable. Should we sacrifice such development flexibility just to fit framework limitations? It's an open question. To play with Checker Framework, you can fetch my example here:
git clone https://github.com/isicju/checker_framework_example
To run your Checker Framework run the following:
mvn clean compile
Checker Framework Alternative: Intellij Idea @NotNull Annotations
Checker Framework is not the only solution and Intellij Idea provides its own annotations @NotNull and @Nullable together with embedded in IDE plugin. Unfortunately, I haven't found a way to add it in the maven compilation step. So, if it exists please let me know in the comments, I'll test it and add it to the article.
Conclusions and My Personal Opinion
Summarizing the whole article I recommend the following:
- Prefer Optional instead of passing Null
- Use Checker Framework
Honestly, in practice, Checker Framework brings limitations to your development and it's questionable if it is worth it or not. If I had to implement my own solution and it had to be stable in production, I would use Checker Framework even if I had to get rid of Lombok or even Builder Pattern.
Opinions expressed by DZone contributors are their own.
Comments