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.
Distinguish between static analysis and runtime analysis. Use static analysis for internal stuff, and runtime analysis for the public boundaries of your code.
For things that should not be null:
Runtime check: Use "if (x == null) ..." (zero dependency) or @javax.validation.NotNull (with bean validation) or @lombok.NonNull (plain and simple) or guavas Preconditions.checkNotNull(...)
Static check: Use an @NonNull annotation
This should give the best result: warnings in the IDE, errors by Findbugs and checkerframework, meaningful runtime exceptions.
Do not expect static checks to be mature, their naming is not standardized and different libraries and IDEs treat them differently, ignore them. The JSR305 javax.annotations.* classes look like standard, but they are not, and they cause split packages with Java9+.
Some notes explanations:
Before Java9, this is my recommendation:
// file: package-info.java
@javax.annotation.ParametersAreNonnullByDefault
package example;
// file: PublicApi
package example;
public interface PublicApi {
Person createPerson(
// NonNull by default due to package-info.java above
String firstname,
String lastname);
}
// file: PublicApiImpl
public class PublicApiImpl implements PublicApi {
public Person createPerson(
// In Impl, handle cases where library users still pass null
@Nullable String firstname, // Users might send null
@Nullable String lastname // Users might send null
) {
if (firstname == null) throw new IllagalArgumentException(...);
if (lastname == null) throw new IllagalArgumentException(...);
return doCreatePerson(fistname, lastname, nickname);
}
@NonNull // Spotbugs checks that method cannot return null
private Person doCreatePerson(
String firstname, // Spotbugs checks null cannot be passed, because package has ParametersAreNonnullByDefault
String lastname,
@Nullable String nickname // tell Spotbugs null is ok
) {
return new Person(firstname, lastname, nickname);
}
@CheckForNull // Do not use @Nullable here, Spotbugs will ignore it, though IDEs respect it
private Person getNickname(
String firstname,
String lastname) {
return NICKNAMES.get(firstname + ':' + lastname);
}
}
Note that there is no way to make Spotbugs raise a warning when a nullable method parameter is dereferenced (at the time of writing, version 3.1 of Spotbugs). Maybe checkerframework can do that.
Sadly these annotations do not distinguish between the cases of a public method of a library with arbitrary callsites, and non-public methods where each callsite can be known. So the double meaning of: "Indicate that null is undesired, but prepare for null being passed nevertheless" is not possible in a single declaration, hence the above example has different annotations for the interface and the implementation.
For cases where the split interface approach is not practical, the following approach is a compromise:
public Person createPerson(
@NonNull String firstname,
@NonNull String lastname
) {
// even though parameters annotated as NonNull, library clients might call with null.
if (firstname == null) throw new IllagalArgumentException(...);
if (lastname == null) throw new IllagalArgumentException(...);
return doCreatePerson(fistname, lastname, nickname);
}
This helps clients to not pass null (writing correct code), while returning useful errors if they do.