Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which @NotNull Java annotation should I use?

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. Many of the tools seem incompatible with each others' @NotNull/@NonNull/@Nonnull annotation and listing all of them in my code would be terrible to read. Any suggestions of which one is the 'best'? Here is the list of equivalent annotations I've found:

  • javax.validation.constraints.NotNull
    Created for runtime validation, not static analysis.
    documentation

  • edu.umd.cs.findbugs.annotations.NonNull
    Used by FindBugs (dead project) and its successor SpotBugs static analysis and therefore Sonar (now Sonarqube)
    FindBugs documentation, SpotBugs documentation

  • javax.annotation.Nonnull
    This might work with FindBugs too, but JSR-305 is inactive. (See also: What is the status of JSR 305?) source

  • org.jetbrains.annotations.NotNull
    Used by IntelliJ IDEA IDE for static analysis.
    documentation

  • lombok.NonNull
    Used to control code generation in Project Lombok.
    Placeholder annotation since there is no standard.
    source, documentation

  • androidx.annotation.NonNull
    Marker annotation available in Android, provided by annotation package
    documentation

  • org.eclipse.jdt.annotation.NonNull
    Used by Eclipse for static code analysis
    documentation

like image 381
jaxzin Avatar asked Feb 10 '11 22:02

jaxzin


People also ask

What is the use of @NotNull annotation in Java?

@NotNull The @NotNull annotation is, actually, an explicit contract declaring that: A method should not return null. Variables (fields, local variables, and parameters) cannot hold a null value.

What is the difference between @NotNull and @NotEmpty and NotBlank?

@NotNull : The CharSequence, Collection, Map or Array object is not null, but can be empty. @NotEmpty : The CharSequence, Collection, Map or Array object is not null and size > 0. @NotBlank : The string is not null and the trimmed length is greater than zero.

What is the difference between @NotNull and @NotEmpty?

@NotNull: a constrained CharSequence, Collection, Map, or Array is valid as long as it's not null, but it can be empty. @NotEmpty: a constrained CharSequence, Collection, Map, or Array is valid as long as it's not null, and its size/length is greater than zero.

What does @NotNull annotation Bean in Bean property?

@NotNull validates that the annotated property value is not null. @AssertTrue validates that the annotated property value is true.


2 Answers

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:

Syntax

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.

Implementation

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 implementation
  • javax.annotation which has a longer implementation
  • javax.validation.constraints which also calls it @NotNull and has an implementation

The @Nullableannotations 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.

Code

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();     } } 

Support

From my experience, javax.annotation is at least supported by Eclipse and the Checker Framework out of the box.

Summary

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.


Sources

  • 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
like image 168
Ludwig Weinzierl Avatar answered Oct 14 '22 20:10

Ludwig Weinzierl


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:

  1. 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.

  2. 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.

  3. 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.

  4. The framework has a way to annotate APIs you use that aren't already annotated for nullness by using stub files.

like image 28
Bert F Avatar answered Oct 14 '22 19:10

Bert F