Adopt JSpecify nullness annotations for compile-time null safety
Context and Problem Statement
Our Java codebase contains inconsistent handling of nullability. We want to detect null-safety issues earlier at compile time and improve API clarity.
Decision Drivers
- Reduce
NullPointerException
s in production by shifting detection to compile time. - Provide clearer API contracts.
- Keep runtime overhead low.
- Ensure incremental and low-risk adoption.
Considered Options
- Do nothing and keep status quo.
- Use
Objects.requireNonNull
and defensive runtime checks. - Adopt JSpecify annotations.
- Use
assert x == null
.
Decision Outcome
Chosen option: “Adopt JSpecify annotations” because it comes out best (see below).
Consequences
- Earlier detection of potential NPEs.
- Clearer API documentation.
- Requires training and CI integration.
- Some friction with unannotated third-party libraries.
Pros and Cons of the Options
Do nothing and keep status quo
- Good, because no immediate implementation work.
- Bad, because NPEs remain runtime-only problems.
- Bad, because a mix of different approaches leads to bad code.
- Bad, because it increases technical debt.
- Bad, because assumes that non-annotated symbols allow null.
Use Objects::requireNonNull
- Good, because JDK native.
- Good, because no external dependencies.
- Good, because it makes NPEs more visible on runtime.
- Bad, because it adds runtime overhead.
- Bad, because it does not provide compile-time contracts.
- Bad, because it is not self-documenting for API contract.
Adopt JSpecify annotations
- Good, because it offers compile-time null safety detection.
- Good, because it works well with common IDEs.
- Good, because of standardized annotations.
- Good, because incremental adoption possible.
- Good, because static analysis supported.
- Good, because compatibility with Kotlin.
- Good, because it is the consensus among major organizations (Google, Microsoft, Jetbrains etc).
- Bad, because it requires annotation effort.
- Bad, because it requires developer training.
Use assert x == null
- Good, because Java language native.
- Good, because no external dependencies.
- Good, because easily readable.
- Good, because it makes NPEs more visible on runtime.
- Bad, because runs by default only in debug mode with option “-ea”.
- Bad, because it does not provide compile-time null safety.
- Bad, because it is not self-documenting for API contract.