In 1965, Tony Hoare was designing ALGOL W, a structured programming language that was a significant step forward
over low level assembly languages, and that would eventually evolve in to Pascal in the 1970s. Its influence
can be felt in many languages through a lineage that includes C, C++, Java, Python and many others.
But a fateful decision was made in the design of ALGOL W that would haunt programmers for decades to come:
the invention of null
references. That was 60 years ago. Since then, null
references have caused
countless headaches, bugs and security vulnerabilities.
Thirty years later, when Java was being created in the mid 1990s, the designers of the language continued
the tradition of null
references. And once they were baked into the language, it was too late to change
them. It took another 20 years for the Java language to take baby steps towards a solution in the form of
the Optional
class, which was introduced in Java 8. However, Optional
is not a magic bullet, and to this
day, Java programmers still have to deal with null
references. This is a consequence of the strict backwards
compatibility of the Java language. However, null
references are not a necessary evil and can be
managed, as many other languages
have shown. Modern mainstream languages like Rust, Swift and Kotlin have shown that is it possible to
design a language that still feels like C/C++ or Java, but has a safe approach to nullabiliy. Of those, Kotlin
is particularly notable because it is a JVM language that is interoperable with Java, showing a remarkable
balance between safety and interoperability.
Back to Java, there have been attempts to introduce some safety around null
references in the form of
static analysis on top of the base language. A crucial step in that direction was the introduction of annotations,
which happened in Java 5 in 2004, opening the door to adding metadata to the types. The next step should have
been the introduction of nullability annotations, but that’s when things started to get messy.
FindBugs and the never finished JSR 305
In 2006 a PhD student at the University of Maryland and his PhD advisor released a static analysis tool called
FindBugs, which gained popularity in the Java community. One of its features was the ability to detect
potential null pointer dereferences. This required the use of annotations to mark the nullability of
parameters and return values, and so the @Nullable
and @NonNull
annotations were born. Initially they
resided in the edu.umd.cs.findbugs.annotations package.
It was clear that the Java language needed a standard way to express nullability, so in 2006 a Java Specification Request
(JSR 305) was created through the Java Community Process to standardize the
nullability annotations. Even before the JSR was completed, the FindBugs annotations were moved to the proposed
javax.annotation
package, and that is the origin of javax.annotation.Nullable
and javax.annotation.Nonnull
.
However, the JSR was never completed and thus the annotations were never standardized. The JSR has been in
a dormant state for 20 years. Therefore, the despite their official-looking package name, those annotations are not part
of the Java specifications, and therefore they lack any official standing. The only way to get them is to
include a dependency. Furthermore, FindBugs had a complicated history. For a while
it was hosted in SourceForge, and its binary artifacts were published under the Maven packageId
of
net.sourceforge.findbugs
(note that Maven packageId
did not match the Java package name). Later the source code
moved to Google Code (both SourceForge and Google Code were predecessors to GitHub), and the binary artifacts were
published under the Maven packageId
of com.google.code.findbugs
. In particular, the JSR 305 annotations implemented
by FindBugs can still be downloaded by adding the following dependency to your Maven project:
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>...</version>
</dependency>
Note that despite the google
packageId, the annotations are not directly affiliated with Google. It just happens that
the project was hosted in Google Code for a while.
The story does not end there. FindBugs was declared abandoned 10 years later, and
a fork called SpotBugs was created and is still maintained in GitHub to this day. Recognising that JSR 305 is defunct,
the SpotBugs maintainers decided to stop publishing annotations under the
javax.annotation
Java package which they do not own. Instead, they recommend using old FindBugs artifact described
above, or the original FindBugs annotations under edu.umd.cs.findbugs.annotations
. This is an interesting plot
twist, basically a 180 turn, because those annotations were declared “deprecated” by FindBugs when they were (perhaps prematurely)
moved to javax.annotation
20 years ago, but now they have been reinstated by SpotBugs.
There is a common myth that the javax.annotation.Nullable
and sibling Nonnull
annotations have a special
standing in Java, when in fact they do not. They are just annotations like any other that you and me can create.
They do not exist unless they are included via a dependency. And as explained above, they are basically abandonware.
The JSR 303 annotations
In parallel to the JSR 305 annotations, another set of annotations was created in 2009, and unlike the JSR 305,
the JSR 303 annotations were successfully completed and standardized. The purpose of the JSR 303 annotations is to
provide a way to express runtime validation constraints on Java beans, such as @Positive
, @Min
(for numeric
types), @Size
, @NotBlank
(for strings), and crucially for this article, @NotNull
. Note that @NotNull
is
not the same as JSR 305’s @Nonnull
.
In order to use the JSR 303 annotations, they must be included as a dependency. As other JavaEE dependencies, they
used to be published under the javax.validation
package, but then they were moved to the jakarta.validation
:
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.1.1</version> <!-- latest version as of 2025 -->
</dependency>
By itself, that dependency only provides the annotations, but it does not change the semantics of the Java language. In order to actually use the annotations, a validation engine must be included in the classpath. The most popular implementation is Hibernate Validator, which despite its name is not limited to Hibernate, and can be used with other Java beans, like Spring MVC models. The validation happens at runtime, and only after the bean has been created and populated with data. This means that the validation is not part of the type system, and therefore it does not provide compile-time guarantees.
A crucial difference between the JSR 303 annotations and the JSR 305 annotations is the retention policy of
the annotations. The JSR 303 annotations have a retention policy of RUNTIME
, which means that they are available
at runtime for reflection. This is in contrast to the JSR 305 annotations, which have a retention policy of
CLASS
, which means that they are saved in the .class
bytecode file but are not available at runtime,
and therefore can only be used by static analysis tools.
The vendor-specific annotations
Due to the lack of a standard, over the years various vendors have created their own nullability annotations. To name a few,
there is software.amazon.awssdk.annotations.NonNull
from the AWS SDK, lombok.NonNull
from the Lombok library,
org.eclipse.jdt.annotation.NonNull
from the Eclipse JDT and org.springframework.lang.NonNull
from the Spring
Framework. But perhaps the most notable of the vendor-specific annotations are the ones from Jetbrains,
namely org.jetbrains.annotations.NotNull
and org.jetbrains.annotations.Nullable
. The dependency for those is:
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>26.0.2</version>
</dependency>
This dependency can be added to any Java project and has no transitive dependencies. The annotations have a retention
of RUNTIME
. This means that they can be used by static analysis tools, but also at runtime for reflection. This is
crucial, because it allows to annotate Java code in a way that simplifies the interoperability with Kotlin. For example,
a Java class annotated with @NotNull
can be used in Kotlin without having to use the !!
operator because Kotlin
will automatically treat the Java type as non-nullable.
JSpecify
After more than a decade of confusion, Google rallied many of the main players in the Java ecosystem to converge in a standard set of nullability annotations. It took an additional 6 years of deliberation, but in 2024 the release 1.0.0 of the JSpecify annotations was announced. The JSpecify annotations are a minimalistic set of nullability annotations that have the support of Google (Android, Guava), Jetbrains (IntelliJ, Kotlin), Oracle (Java), Meta, Microsoft, and Broadcom (Spring), among others. It also has backing of static analysis tools like SonarQube, NullAway, ErrorProne and The Checker Framework. The JSpecify annotations are carefully specified to avoid some of the pitfalls of JSR 305. Large projects like Spring Framework have already started to adopt the JSpecify annotations, and if everything goes well, they will become the de facto standard for nullability annotations in Java, replacing the failed JSR 305 and the many vendor-specific annotations. As of 2025, JSpecify are already the obvious choice for new projects.
The future of Java
The JSpecify annotations are a step in the right direction, but they are not a complete solution. They do not change the semantics of the Java language, and therefore they do not provide compile-time guarantees. Compared to Kotlin null-safety, they are a bit unsatisfactory.
However, the Java language continues to evolve. When it was created in the mid 1990s, their designers made the
controversial decision to include null
as a valid value of every object reference… but not primitive types like
int
, boolean
and double
. It is too late to change that now, but there is a
JEP proposal that derives from Project Valhalla and
value types. The idea is to introduce type markers to indicate that some (value) types are null-restricted. If this
comes to fruition, it will not apply to any object reference or to existing code, but it will allow new code to
be written in a more null-safe way. However, at this point this is just a proposal, and it is not clear
when or if it will be implemented.