Null Checking Framework Selection
Context and Problem Statement
Null pointer exceptions are a pervasive source of bugs in Java. To alleviate this, we want a compile-time null‐checking / nullness analysis framework. Two major contenders are Checker Framework and Error Prone (with NullAway). We also want our solution to align with modern nullness specification efforts such as JSpecify.
Decision Drivers
- Static prevention of null dereference errors.
- Developer experience: low friction, good IDE, and build integration.
- Scalability to our codebase.
- Interoperability with JSpecify and future ecosystem alignment.
- Incremental adoption: we should not be forced to annotate everything at once.
Considered Options
- Error Prone + NullAway
- Checker Framework
- No nullness analysis (status quo)
Decision Outcome
Chosen option: “Error Prone + NullAway” because it comes out best (see below).
Consequences
- We will annotate public APIs (interfaces, service endpoints, widely used modules) using JSpecify
@Nullable
/@NonNull
(or@NullMarked
scoping) progressively. - We enable NullAway in CI builds; thus violations break the build.
- NullAway may not check some corner cases (especially around generics); therefore, manual reviews need to keep that in mind.
- Developers must be trained on JSpecify and on how NullAway behaves.
- Over time, we gradually annotate more of the codebase; initially non-annotated code will be treated as “unspecified nullness” until annotated.
Pros and Cons of the Options
Checker Framework
- Good, because it supports advanced nullness contracts, refinement, and pluggability.
- Bad, because compile times increase significantly.
- Bad, because it lacks support for JSpecify’s
@NullMarked
/@NullUnmarked
scopes.
Error Prone + NullAway
- Good, because it is lightweight, fast, and has a low overhead.
- Good, because support for JSpecify is built-in.
- Good, because it fully supports JSpecify annotations.
- Good, because it is endorsed by JUnit 6.
- Bad, because it lacks support for advanced nullness contracts, refinement, and pluggability, and misses some edge cases.
No Nullness Framework
- Good, because zero overhead or tooling complexity.
- Bad, because we suffer from null pointer exceptions at runtime.