Set Java Nonnull/Nullable Annotation By Package for Cleaner Code
Posted on March 8, 2017
What is a Java Null Annotation?
Java null annotation (or nullability annotation) contracts are a way to mark the code in a way that static checkers know which parameters, variables or return types can be null. You would especially want to do that if you hate defensive null checks everywhere in the code, like this one:
public String normalizeString(String s) { if (s == null) { //Dreadful null check! GRRRRRR!!! return ""; } return s.toLowerCase(); }
So what is the workaround?
If you are sure that parameter s must not ever be null, annotate it as such:
public String normalizeString(@Nonnull String s) {
Now the IDE should apply static checks and highlight any violations of your java nullability contracts.
The annotations I like to use are javax.annotation.Nonnull and javax.annotation.Nullable from findbugs jsr305. Its Maven dependency can be found here.
What is Static Checking
Simply put, a static checking tool analyses your code without actually running it. What that tool can do to help, depends on a great deal. Usually, it highlights an issue and shows a warning. Intellij Idea and Eclipse have static checkers for a number of things, including nullability. I encourage you to take a look over Idea’s inspections here to find out more. Note that not all inspections are static checkers, but does it really matter?
How to Use Java Null Annotations
To reap the benefits of these annotations you must:
- Add the com.google.code.findbugs:jsr305 dependency to your project.
- Annotate all the @Nullable and @Nonnull fields, variables, parameters and return types.
- See if the IDE picks up on the hints and highlights violations. If not, see if you need to configure the nullability checker from the IDE configs.
Streamline Nullability with Package Level Null Annotations
Because of the binary nature of the nullability concept, we can easily set one as default. What this does is cut down the annotating in half, or less. The approach I use sets Nonnull as the default. That is to encourage having mainly non-null references. This way, I probably end up with less than half the annotations I would otherwise have… And fewer null checks!
I will go ahead and re-post the code I found in this StackOverflow thread. It is a custom annotation defined to be applied using package-info.java files:
package ro.stancalau; import javax.annotation.Nonnull; import javax.annotation.meta.TypeQualifierDefault; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * This annotation can be applied to a package, class or method to indicate that the class fields, * method return types and parameters in that element are not null by default unless there is: * An explicit nullness annotation * The method overrides a method in a superclass (in which case the annotation * of the corresponding parameter in the superclass applies) there is a * default parameter annotation applied to a more tightly nested element. * * @see http://stackoverflow.com/a/9256595/14731 */ @Documented @Nonnull @TypeQualifierDefault({ ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface NotNullByDefault {}
Your package-info.java files should look like this:
@NotNullByDefault package the.package.name; import ro.stancalau.NotNullByDefault;
Note: You need to copy the package-info.java files in all the package folders. Otherwise, the nullability contract won’t be applied for that package. In a way, you get away with half the annotations in the code, but need to put up with these files in the source directories.
Keeping track of all those package-info.java files can be avoided using the technique I discuss my article about Java Default Package-Info Generation with Gradle.
Sample Project
For a real-life example, please refer to this GitHub project that showcases how default nullability works.
Disclaimer
BEWARE: Using @Nonnull annotations from findbugs-jsr305 does not guarantee that null references cannot get passed! Do not forget to do input validation especially if your public API gets called from other systems. Also, make sure you inspect all your codebase for nullability contract violations before pushing your changes to VCS.
In addition, findbugs-jsr305 nullability annotations do not act in any way as validation mechanisms. In fact, they are completely ignored and have no effect on the code at runtime.
What do you think? Share your thoughts!