Disclaimer
- The article does not claim to the discovery of America and is popularizing and abstract in nature. Ways of dealing with NPE in code are far from new, but much less well known than we would like.
- A single NPE is probably the simplest of all possible errors. We are talking about a situation where, due to the lack of a policy for their processing, the NPE dominates.
- This article does not discuss approaches that are not applicable for Java 6 and 7 (the MayBe monad, JSR-308, and Type Annotations monad).
- Ubiquitous defensive programming is not considered as a method of struggle, as it is very littering the code, reduces performance and, as a result, still does not give the desired effect.
- There may be some discrepancies in the terminology used and generally accepted. The same description of the checks used by Intellij Idea does not pretend to be complete and accurate, as it is taken from the documentation and the observed behavior, and not the source code.
JSR-305 to the rescue
Here I want to share my practice, which helps me to write almost completely NPE-free code. Its main idea is to use annotations about the optional values from the library that implements JSR-305 (com.google.code.findbugs: jsr305: 1.3.9):
- @Nullable - annotated value is optional;
- @Nonnull - vice versa, respectively.
Naturally, both annotations are applicable to the fields of objects and classes, arguments and return values of methods, local variables. Thus, these annotations supplement the type information regarding the necessity of the presence of a value.
But annotate everything for a long time and the readability of the code is sharply reduced. Therefore, as a rule, the project team accepts the agreement that everything that is not marked
@Nullable
is mandatory. Those who used Guava, Guice are well acquainted with this practice.
')
Here is an example of a possible code for such an abstract project:
import javax.annotation.Nullable; public abstract class CodeSample { public void correctCode() { @Nullable User foundUser = findUserByName("vasya"); if(foundUser == null) { System.out.println("User not found"); return; } String fullName = Asserts.notNull(foundUser.getFullName()); System.out.println(fullName.length()); } public abstract @Nullable User findUserByName(String userName); private static class User { private String name; private @Nullable String fullName; public User(String name, @Nullable String fullName) { this.name = name; this.fullName = fullName; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Nullable public String getFullName() { return fullName; } public void setFullName(@Nullable String fullName) { this.fullName = fullName; } } }
As you can see everywhere it is clear whether it is possible to get null with reference dereference.
The only caveat is that there are situations where, in the current context (eg, at a certain stage of the business process), we know for sure that something in general need not be present. In our case, this is the full name of Basil, which may in principle be absent by the user, but we know that here and now it is impossible according to the rules of business logic. For such situations, I use a simple assert utility:
import javax.annotation.Nullable; public class Asserts { public static <T> T notNull(@Nullable T obj) { if(obj == null) { throw new IllegalStateException(); } return obj; } }
Real java asserts can also be used, but I haven’t got accustomed because of the need to explicitly include in the runtime and less convenient syntax.
A few words about inheritance and covariance / contravariance:
- if the return type of the ancestor's method is NotNull, then the overridden method of the heir must also be NotNull. The rest is valid;
- if the argument of the ancestor method is Nullable, then the overridden method of the heir must also be Nullable. The rest is valid.
In fact, this is quite enough and static analysis (in the IDE or on the CI) is not particularly needed. But let the IDE work, no wonder they bought it. I prefer to use Intellij Idea, so all further examples will be on it.
Intellij Idea makes life better
I’ll say right away that by default Idea offers its annotations with similar semantics, although it understands all the others. This can be changed in Settings -> Inspections -> Probable bugs -> {Constant conditions & exceptions;
@NotNull/@Nullable
problems}. In both inspections, you must select the pair of annotations used.
Here's how Idea looks like highlighting errors found by inspections in the incorrect implementation of the previous code:

It became quite wonderful, the IDE not only finds two NPEs, but also forces us to do something with them.
It would seem that everything is fine, but the Idea integrated static analyzer does not understand the default agreement we have adopted. From her point of view (as well as any other stat. Analyzer) three options appear here:
- Nullable - value is required;
- NotNull - value is optional;
- Unknown - nothing is known about the mandatory value.
And all that we did not mark is now considered Unknown. Is this a problem? To answer this question, you need to understand what Idea inspections for Nullable and NotNull can find:
- dereference of a variable potentially containing null when accessing a field or method of an object;
- passing the NotNull argument to a nullable variable;
- redundant check for missing value for NotNull variable;
- non-compliance of mandatory parameters when assigning a value;
- returning a NotNull method with a nullable variable in one of the branches.
Logically, any value returned from a library method that is not marked up by these annotations is Unknown. To combat this, it is enough to annotate a local variable or field that is used for assignment.
If we continue to adhere to our practice, then our code will remain marked as Nullable all optional. Thus, the first check continues to work, protecting us from many NPEs. Unfortunately, all other checks have fallen off. The second test, which is extremely useful against comrades who are very fond of how to write methods that actively accept null as arguments, and pass null to other methods that are not designed for this, does not work either.
You can restore the behavior of the second check in two ways:
- In the settings of the Constant conditions & exceptions inspection, activate the option "Return to non-annotated parameters". This will result in all non-annotated method arguments for the entire project being considered NotNull. For a project that is just starting out, this solution is perfect, but for obvious reasons it is not appropriate when introducing practice into a project with a significant existing code base;
- use the
@ParametersAreNonnullByDefault
annotation to set the appropriate behavior in a specific scope, which can be a method, class, package. This solution is already great for legacy project. A fly in the ointment is that when defining behavior for a package, the recursion is not supported and the annotation cannot be hung on the entire module at a time.
In both cases, the default NotNull are only non-annotated method arguments. Fields, local variables and return values are not affected.
Near future
The upcoming support for
@TypeQualifierDefault
, which already works in Intellij Idea 14 EAP, is intended to improve the situation. Using them, you can define
your @NonNullByDefault
annotation , which will determine the default binding for everything, maintaining the same scopes. There is no recursiveness now either, but the debate is
on .
Below is shown how the inspections look for three cases of working from legacy code with a code in a new style with annotations.
We annotate explicitly:

By default only arguments:

By default, all:

the end
Now everything has become almost wonderful, it remains to wait for Intellij Idea 14 to be released. The only thing that is still missing is complete ability to annotate a type in Generic before Java 8 and its support for Type annotations. That is not enough for ListenableFutures and collections in some cases.
Since the volume of the article turned out to be quite significant, most of the examples were left out but available
here .
upd. It’s still
possible to add meta information about optional values for external libraries.
Used sources
- stackoverflow.com/questions/16938241/is-there-a-nonnullbydefault-annotation-in-idea
- www.jetbrains.com/idea/webhelp/annotating-source-code.html
- youtrack.jetbrains.com/issue/IDEA-65566
- youtrack.jetbrains.com/issue/IDEA-125281