What is the the most common crash you face in daily Java development? Probably the NullPointerException!

1 Nullable & NonNull

There are lots of discussion about why Null is a bad idea and more modern languages like Swift and Kotlin are trying to get rid of this exception by other approach. But face the truth, you need to add check when you access variable in Java to avoid the NullPointerException

@Nullable and @NotNull annotations let you check nullability of a variable, parameter, or return value. They help you control contracts throughout method hierarchies, and if your IDE spots that the contract is being violated, it will report the detected problem, and will point to the code where NullPointerException may occur

Copy
public void someFunction(A a) {
  if (a != null) {
    a.doSomething();
  }
}
A = new A();
a = null; // you'll never know when will somebody add this.
someFunction(a);

It’s never too aggressive to fight against the NullPointerException, but it’s even better if we can get more context about what the variable will/should be. That’s why the eclipse project provides @Nullable and @NonNull to help us distinguish the nullability.

1.1 The @NonNull Annotation

The @NonNull annotation is the most important among all the annotations of the null-safety feature. We can use this annotation t0 declare non-null constraint anywhere an object reference is expected: a field, a method parameter or a method's return value

Suppose we have a class named Person:

Copy
public class Person {
    private String fullName;

    void setFullName(String fullName) {
        if (fullName != null && fullName.isEmpty()) {
            fullName = null;
        }
        this.fullName = fullName;
    }

}

This class definition is valid, but has a defect – the fullName field may be set to null. If this happens, we could end up with an NPE when working with fullName.

The static null-safety analysis enables tools to report such a danger. For instance, if we write code in IntelliJ IDEA and decorate the fullName field with the @NonNull annotation, we'll see a warning:

Copy
public class Person {
    
    @NonNull
    private String fullName;

    void setFullName(String fullName) {
        if (fullName != null && fullName.isEmpty()) {
            fullName = null;
        }
        this.fullName = fullName; <--- WARNING Expression 'fullName' might evaluate to null but is assigned to a variable annotated with @NonNull
    }

}

Thanks to this indication, we're aware of the problem in advance and able to take appropriate action to avoid a runtime failure.

1.2 The @NonNullByDefault Annotation

The @NonNull annotation is helpful in guaranteeing null-safety. However, we would pollute the whole code base if adorning all non-null fields with this annotation.

We can avoid the abuse of @NonNull with another annotation – @NonNullByDefault. This annotation is applicable at the package level, notifying our development tools that all fields in the annotated package are, by default, non-null

For the @NonNullByDefault annotation to kick in, we need to create a file named package-info.java in the root directory of the package and annotate the package with @NonNullByDefault:

Copy
@NonNullByDefault
package deister.axional.studio.shared.apps.model.db.dict.data.obj.base;

TLet's declare another property in the Person class, called nickName:

Copy
package deister.axional.studio.shared.apps.model.db.dict.data.obj.base;

// import statements

public class Person {
    private String nickName;

    void setNickName(@Nullable String nickName) {
        if (nickName != null && nickName.isEmpty()) {
            nickName = null;
        }
        this.nickName = nickName;
    }

    // other declarations
}

This time, we don't embellish the nickName field with @NonNull but still see a warning

The @NonNullByDefault annotation makes our code less verbose while ensuring the same level of safety that @NonNull provides.

1.2.1 Fine tuning

The exact effect is further controlled by the attribute value, specifying what kinds of locations within the given declaration will be affected.Locations are defined in org.eclipse.jdt.annotation.DefaultLocation enum:

PARAMETER
Defines that a given @NonNullByDefault annotation should affect all unannotated parameters of any method or constructor within the scope of the annotated declaration.
Copy
@NonNullByDefault(PARAMETER)
	   interface X {
	       void print(Number n);
	   }

Here Number will be interpreted as @NonNull Number.

RETURN_TYPE
Defines that a given NonNullByDefault annotation should affect all unannotated method return types within the scope of the annotated declaration.
Copy
@NonNullByDefault(RETURN_TYPE)
	   interface X {
	       Number getNumber();
	   }

Here Number will be interpreted as @NonNull Number.

FIELD
Defines that a given NonNullByDefault annotation should affect all unannotated field types within the scope of the annotated declaration.
Copy
@NonNullByDefault(FIELD)
	   class X {
	       Number number = Integer.MAX_VALUE;
	   }

Here Number will be interpreted as @NonNull Number.

TYPE_PARAMETER
Defines that a given NonNullByDefault annotation should affect all unannotated type parameter declarations within the scope of the annotated declaration.
Copy
@NonNullByDefault(TYPE_PARAMETER)
	   class X {
	       <T> T identity(T t) { return t; }
	   }

Here <T> will be interpreted as <@NonNull T>.

TYPE_BOUND
Defines that a given NonNullByDefault annotation should affect all unannotated explicit type bounds within the scope of the annotated declaration. A type bound of type Object is never considered as an explicit bound, i.e., T extends Object is never affected by NonNullByDefault.
Copy
@NonNullByDefault(TYPE_BOUND)
	   interface X {
	       <T extends Number> void process(T t, List<? super Number> l);
	   }

Here both occurrences of Number will be interpreted as @NonNull Number.

TYPE_ARGUMENT
Defines that a given NonNullByDefault annotation should affect all unannotated type arguments within the scope of the annotated declaration (except wildcards and type variables).
Copy
@NonNullByDefault(TYPE_ARGUMENT)
	   interface X<T> {
	       void process(List<T> tl, List<Number> nl);
	   }

Here Number will be interpreted as @NonNull Number, but the use of type variable T is not affected.

ARRAY_CONTENTS
Defines that a given NonNullByDefault annotation should affect all unannotated array components within the scope of the annotated declaration.
Copy
@NonNullByDefault(ARRAY_CONTENTS)
	   interface X {
	       Number[] n1;
	       Number[][] n2;
	   }

These declarations are interpreted as:

Copy
@NonNull Number [] n1;
	      @NonNull Number [] @NonNull[] n2;
I.e., both fields can still be null (see the unannotated left-most pair of brackets) but none of the contents of these arrays is allowed to be null (at any dimension).

1.3 The @Nullable Annotation

The @NonNullByDefault annotation is generally preferable to @NonNull as it helps reduce boilerplate. At times we want to exempt some fields from the non-null constraint specified at the package level.

Let's go back to the nickName field in and decorate it with the @Nullable annotation::

Copy
@Nullable
private String nickName;
}

The warning we saw before is gone now.

In this situation, we used the @Nullable annotation to override the semantics of @NonNullFields on a field.

2 Installation

Simply add dependency in your build.gradle.

Copy
implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.600'

2.1 Setting up Intellij

The check is done by the Constant conditions & exceptions and @NotNull/@Nullable problems inspections. You can configure the way these inspections work in the Settings/Preferences⌘ , dialog. Go to Editor | Inspections | Java | Probable bugs

To tell Intellij IDEA that we're using annotation from org.eclipse.jdt.annotation package, go to Settings/Preferences | Build, Execution, Deployment | Compiler and use the Configure annoations button

2.2 Setting up Eclipse

The check is done by the Constant conditions & exceptions and @NotNull/@Nullable problems inspections. You can configure the way these inspections work in the Properties⌘ , dialog. Go to Java Compiler | Errors Warnings and tick the checkbox Enable annotation-based null analysis

3 Using the annotations

Copy
public void someFunction1(@Nullable A a) {
  if (a != null) { // warn if we don't add null check
    a.doSomething();
  }
}
public void someFunction2(@NonNull A a) {
  a.doSomething();
}
A a = new A();
a = null;
someFunction1(a);
someFunction2(a); // warn a is null;

If any parameter is mark as @NonNull, we can stripe the null check with confidant. On the other hand, if the parameter is mark as @Nullable and we don’t add null check inside the function, the IDE will warn you with lint error and some visual hint

The @NonNull/@Nullable annotation can put in front of functions as well to indicate the return value nullability.

Copy
@NonNull
public A getA() {
  return null; // warn: ‘null’ is returned by the method ...
}