I\'m looking to make my code more readable as well as use tooling like IDE code inspection and/or static code analysis (FindBugs and Sonar) to avoid NullPointerExceptions.
Just pointing out that the Java Validation API (javax.validation.constraints.*
) doesn't come with a @Nullable
annotation, which is very valuable in a static analysis context. It makes sense for runtime bean validation as this is the default for any non-primitive field in Java (i.e. nothing to validate/enforce). For the purposes stated that should weigh towards the alternatives.
According to the Java 7 features list JSR-308 type annotations are deferred to Java 8. JSR-305 annotations are not even mentioned.
There is a bit of info on the state of JSR-305 in an appendix of the latest JSR-308 draft. This includes the observation that JSR-305 annotations seem to be abandoned. The JSR-305 page also shows it as "inactive".
In the mean time, the pragmatic answer is to use the annotation types that are supported by the most widely used tools ... and be prepared to change them if the situation changes.
In fact, JSR-308 does not define any annotation types/classes, and it looks like they think it is out of scope. (And they are right, given the existence of JSR-305).
However, if JSR-308 really looks like making it into Java 8, it wouldn't surprise me if interest in JSR-305 revived. AFAIK, the JSR-305 team hasn't formally abandoned their work. They have just been quiet for 2+ years.
It is interesting that Bill Pugh (the tech lead for JSR-305) is one of the guy behind FindBugs.
Since JSR 305 (whose goal was to standardize @NonNull
and @Nullable
) has been dormant for several years, I'm afraid there is no good answer. All we can do is to find a pragmatic solution and mine is as follows:
From a purely stylistic standpoint I would like to avoid any reference to IDE, framework or any toolkit except Java itself.
This rules out:
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
org.checkerframework.checker.nullness.qual
lombok.NonNull
Which leaves us with either javax.validation.constraints
or javax.annotation
.
The former comes with JEE. If this is better than javax.annotation
, which might come eventually with JSE or never at all, is a matter of debate.
I personally prefer javax.annotation
because I wouldn't like the JEE dependency.
This leaves us with
javax.annotation
which is also the shortest one.
There is only one syntax which would even be better: java.annotation.Nullable
. As other packages graduated
from javax
to java
in the past, the javax.annotation would
be a step in the right direction.
I was hoping that they all have basically the same trivial implementation, but a detailed analysis showed that this is not true.
First for the similarities:
The @NonNull
annotations all have the line
public @interface NonNull {}
except for
org.jetbrains.annotations
which calls it @NotNull
and has a trivial implementationjavax.annotation
which has a longer implementationjavax.validation.constraints
which also calls it @NotNull
and has an implementationThe @Nullable
annotations all have the line
public @interface Nullable {}
except for (again) the org.jetbrains.annotations
with their trivial implementation.
For the differences:
A striking one is that
javax.annotation
javax.validation.constraints
org.checkerframework.checker.nullness.qual
all have runtime annotations (@Retention(RUNTIME)
), while
android.support.annotation
edu.umd.cs.findbugs.annotations
org.eclipse.jdt.annotation
org.jetbrains.annotations
are only compile time (@Retention(CLASS)
).
As described in this SO answer the impact of runtime annotations is smaller than one might think, but they have the benefit of enabling tools to do runtime checks in addition to the compile time ones.
Another important difference is where in the code the annotations can be used. There are two different approaches. Some packages use JLS 9.6.4.1 style contexts. The following table gives an overview:
FIELD METHOD PARAMETER LOCAL_VARIABLE android.support.annotation X X X edu.umd.cs.findbugs.annotations X X X X org.jetbrains.annotation X X X X lombok X X X X javax.validation.constraints X X X
org.eclipse.jdt.annotation
, javax.annotation
and org.checkerframework.checker.nullness.qual
use the contexts defined in
JLS 4.11, which is in my opinion the right way to do it.
This leaves us with
javax.annotation
org.checkerframework.checker.nullness.qual
in this round.
To help you to compare further details yourself I list the code of every annotation below.
To make comparison easier I removed comments, imports and the @Documented
annotation.
(they all had @Documented
except for the classes from the Android package).
I reordered the lines and @Target
fields and normalized the qualifications.
package android.support.annotation;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER})
public @interface NonNull {}
package edu.umd.cs.findbugs.annotations;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
public @interface NonNull {}
package org.eclipse.jdt.annotation;
@Retention(CLASS)
@Target({ TYPE_USE })
public @interface NonNull {}
package org.jetbrains.annotations;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
public @interface NotNull {String value() default "";}
package javax.annotation;
@TypeQualifier
@Retention(RUNTIME)
public @interface Nonnull {
When when() default When.ALWAYS;
static class Checker implements TypeQualifierValidator<Nonnull> {
public When forConstantValue(Nonnull qualifierqualifierArgument,
Object value) {
if (value == null)
return When.NEVER;
return When.ALWAYS;
}
}
}
package org.checkerframework.checker.nullness.qual;
@Retention(RUNTIME)
@Target({TYPE_USE, TYPE_PARAMETER})
@SubtypeOf(MonotonicNonNull.class)
@ImplicitFor(
types = {
TypeKind.PACKAGE,
TypeKind.INT,
TypeKind.BOOLEAN,
TypeKind.CHAR,
TypeKind.DOUBLE,
TypeKind.FLOAT,
TypeKind.LONG,
TypeKind.SHORT,
TypeKind.BYTE
},
literals = {LiteralKind.STRING}
)
@DefaultQualifierInHierarchy
@DefaultFor({TypeUseLocation.EXCEPTION_PARAMETER})
@DefaultInUncheckedCodeFor({TypeUseLocation.PARAMETER, TypeUseLocation.LOWER_BOUND})
public @interface NonNull {}
For completeness, here are the @Nullable
implementations:
package android.support.annotation;
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD})
public @interface Nullable {}
package edu.umd.cs.findbugs.annotations;
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
@Retention(CLASS)
public @interface Nullable {}
package org.eclipse.jdt.annotation;
@Retention(CLASS)
@Target({ TYPE_USE })
public @interface Nullable {}
package org.jetbrains.annotations;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
public @interface Nullable {String value() default "";}
package javax.annotation;
@TypeQualifierNickname
@Nonnull(when = When.UNKNOWN)
@Retention(RUNTIME)
public @interface Nullable {}
package org.checkerframework.checker.nullness.qual;
@Retention(RUNTIME)
@Target({TYPE_USE, TYPE_PARAMETER})
@SubtypeOf({})
@ImplicitFor(
literals = {LiteralKind.NULL},
typeNames = {java.lang.Void.class}
)
@DefaultInUncheckedCodeFor({TypeUseLocation.RETURN, TypeUseLocation.UPPER_BOUND})
public @interface Nullable {}
The following two packages have no @Nullable
, so I list them separately; Lombok has a pretty boring @NonNull
.
In javax.validation.constraints
the @NonNull
is actually a @NotNull
and it has a longish implementation.
package lombok;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
public @interface NonNull {}
package javax.validation.constraints;
@Retention(RUNTIME)
@Target({ FIELD, METHOD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Constraint(validatedBy = {})
public @interface NotNull {
String message() default "{javax.validation.constraints.NotNull.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@interface List {
NotNull[] value();
}
}
From my experience, javax.annotation
is at least supported by Eclipse and the Checker Framework out of the box.
My ideal annotation would be the java.annotation
syntax with the Checker Framework implementation.
If you don't intend to use the Checker Framework the javax.annotation
(JSR-305) is still your best bet for the time being.
If you are willing to buy into the Checker Framework just use
their org.checkerframework.checker.nullness.qual
.
android.support.annotation
from android-5.1.1_r1.jar
edu.umd.cs.findbugs.annotations
from findbugs-annotations-1.0.0.jar
org.eclipse.jdt.annotation
from org.eclipse.jdt.annotation_2.1.0.v20160418-1457.jar
org.jetbrains.annotations
from jetbrains-annotations-13.0.jar
javax.annotation
from gwt-dev-2.5.1-sources.jar
org.checkerframework.checker.nullness.qual
from checker-framework-2.1.9.zip
lombok
from lombok
commit f6da35e4c4f3305ecd1b415e2ab1b9ef8a9120b4
javax.validation.constraints
from validation-api-1.0.0.GA-sources.jar
I very much like the Checker Framework, which is an implementation of type annotations (JSR-308) which is used to implement defect checkers like a nullness checker. I haven't really tried any others to offer any comparison, but I've been happy with this implementation.
I'm not affiliated with the group that offers the software, but I am a fan.
Four things I like about this system:
It has a defect checkers for nullness (@Nullable), but also has ones for immutability and interning (and others). I use the first one (nullness) and I'm trying to get into using the second one (immutability/IGJ). I'm trying out the third one, but I'm not certain about using it long term yet. I'm not convinced of the general usefulness of the other checkers yet, but its nice to know that the framework itself is a system for implementing a variety of additional annotations and checkers.
The default setting for nullness checking works well: Non-null except locals (NNEL). Basically this means that by default the checker treats everyhing (instance variables, method parameters, generic types, etc) except local variables as if they have a @NonNull type by default. Per the documentation:
The NNEL default leads to the smallest number of explicit annotations in your code.
You can set a different default for a class or for a method if NNEL doesn't work for you.
This framework allows you to use with without creating a dependency on the framework by enclosing your annotations in a comment: e.g. /*@Nullable*/
. This is nice because you can annotate and check a library or shared code, but still be able to use that library/shared coded in another project that doesn't use the framework. This is a nice feature. I've grown accustom to using it, even though I tend to enable the Checker Framework on all my projects now.
The framework has a way to annotate APIs you use that aren't already annotated for nullness by using stub files.