Skip to content

Commit

Permalink
Update null-safety documentation
Browse files Browse the repository at this point in the history
This commit documents using JSpecify instead of the now deprecated
Spring null-safety annotations.

Closes spring-projectsgh-28797
  • Loading branch information
sdeleuze committed Dec 19, 2024
1 parent b358656 commit 6d2d10c
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 67 deletions.
156 changes: 117 additions & 39 deletions framework-docs/modules/ROOT/pages/core/null-safety.adoc
Original file line number Diff line number Diff line change
@@ -1,57 +1,135 @@
[[null-safety]]
= Null-safety

Although Java does not let you express null-safety with its type system, the Spring Framework
provides the following annotations in the `org.springframework.lang` package to let you
declare nullability of APIs and fields:
Although Java does not let you express null-safety with its type system, the Spring Framework codebase is annotated with
https://jspecify.dev/docs/start-here/[JSpecify] annotations to declare the nullability of APIs, fields and related type
usages. Reading the https://jspecify.dev/docs/user-guide/[JSpecify user guide] is highly recommended in order to get
familiar with those annotations and semantics.

* {spring-framework-api}/lang/Nullable.html[`@Nullable`]: Annotation to indicate that a
specific parameter, return value, or field can be `null`.
* {spring-framework-api}/lang/NonNull.html[`@NonNull`]: Annotation to indicate that a specific
parameter, return value, or field cannot be `null` (not needed on parameters, return values,
and fields where `@NonNullApi` and `@NonNullFields` apply, respectively).
* {spring-framework-api}/lang/NonNullApi.html[`@NonNullApi`]: Annotation at the package level
that declares non-null as the default semantics for parameters and return values.
* {spring-framework-api}/lang/NonNullFields.html[`@NonNullFields`]: Annotation at the package
level that declares non-null as the default semantics for fields.
The primary goal of this explicit null-safety arrangement is to prevent `NullPointerException` to be thrown at runtime via
build time checks and to turn explicit nullability into a way to express the possible absence of value. It is useful in
both Java by leveraging some tooling (https://github.com/uber/NullAway[NullAway] or IDEs supporting null-safety
annotations such as IntelliJ IDEA or Eclipse) and Kotlin where JSpecify annotations are automatically translated to
{kotlin-docs}/null-safety.html[Kotlin's null safety].

The Spring Framework itself leverages these annotations, but they can also be used in any
Spring-based Java project to declare null-safe APIs and optionally null-safe fields.
Nullability declarations for generic type arguments, varargs, and array elements are not supported yet.
Nullability declarations are expected to be fine-tuned between Spring Framework releases,
including minor ones. Nullability of types used inside method bodies is outside the
scope of this feature.
`@Nullable` annotations are also used at runtime to infer if a parameter is optional or not, for example via
{spring-framework-api}/core/MethodParameter.html#isOptional()[`MethodParameter#isOptional`].

NOTE: Other common libraries such as Reactor and Spring Data provide null-safe APIs that
use a similar nullability arrangement, delivering a consistent overall experience for
Spring application developers.
[[null-safety-libraries]]
== Annotating libraries with JSpecify annotations

As of Spring Framework 7, the Spring Framework codebase leverages JSpecify annotations to expose null-safe APIs and
to check the consistency of those null-safety declarations with https://github.com/uber/NullAway[NullAway] as part of
its build. It is recommended for each library depending on Spring Framework (Spring portfolio projects), as
well as other libraries related to the Spring ecosystem (Reactor, Micrometer and Spring community projects), to do the
same.

[[null-safety-applications]]
== Leveraging JSpecify annotations in Spring applications

Developing applications with IDEs supporting null-safety annotations, such as IntelliJ IDEA or Eclipse, will provide
warnings in Java and errors in Kotlin when the null-safety contracts are not honored, allowing Spring application
developers to refine their null handling to prevent `NullPointerException` to be thrown at runtime.

[[use-cases]]
== Use cases
Optionally, Spring application developers can annotate their codebase and use https://github.com/uber/NullAway[NullAway]
to enforce null-safety during build time at application level.

In addition to providing an explicit declaration for Spring Framework API nullability,
these annotations can be used by an IDE (such as IDEA or Eclipse) to provide useful
warnings related to null-safety in order to avoid `NullPointerException` at runtime.
[[null-safety-guidelines]]
== Guidelines

They are also used to make Spring APIs null-safe in Kotlin projects, since Kotlin natively
supports {kotlin-docs}/null-safety.html[null-safety]. More details
are available in the xref:languages/kotlin/null-safety.adoc[Kotlin support documentation].
The purpose of this section is to share some guidelines proposed for using JSpecify annotations in the context of
Spring-related libraries or applications.

The key points to understand is that by default, the nullability of types is unknown in Java, and that non-null type
usages are by far more frequent than nullable ones. In order to keep codebases readable, we typically want to define
that by default, type usages are non-null unless marked as nullable for a specific scope. This is exactly the purpose of
https://jspecify.dev/docs/api/org/jspecify/annotations/NullMarked.html[`@NullMarked`] that is typically set with Spring
at package level via a `package-info.java` file, for example:

[source,java,subs="verbatim,quotes",chomp="-packages",fold="none"]
----
@NullMarked
package org.springframework.core;
import org.jspecify.annotations.NullMarked;
----

[[jsr-305-meta-annotations]]
== JSR-305 meta-annotations
In the various Java files belonging to the package, nullable type usages are defined explicitly with
https://jspecify.dev/docs/api/org/jspecify/annotations/Nullable.html[`@Nullable`]. It is recommended that this
annotation is specified just before the related type.

Spring annotations are meta-annotated with {JSR}305[JSR 305]
annotations (a dormant but widespread JSR). JSR-305 meta-annotations let tooling vendors
like IDEA or Kotlin provide null-safety support in a generic way, without having to
hard-code support for Spring annotations.
For example, for a field:

[source,java,subs="verbatim,quotes"]
----
private @Nullable String fileEncoding;
----

Or for method parameters and return value:

[source,java,subs="verbatim,quotes"]
----
public static @Nullable String buildMessage(@Nullable String message,
@Nullable Throwable cause) {
// ...
}
----

When overriding a method, nullability annotations are not inherited from the superclass method. That means those
nullability annotations should be repeated if you just want to override the implementation and keep the same API
nullability.

With arrays and varargs, you need to be able to differentiate the nullability of the elements from the nullability of
the array itself. Pay attention to the syntax
https://docs.oracle.com/javase/specs/jls/se17/html/jls-9.html#jls-9.7.4[defined by the Java specification] which may be
initially surprising:

- `@Nullable Object[] array` means individual elements can be null but the array itself can't.
- `Object @Nullable [] array` means individual elements can't be null but the array itself can.
- `@Nullable Object @Nullable [] array` means both individual elements and the array can be null.

The Java specifications also enforces that annotations defined with `@Target(ElementType.TYPE_USE)` like JSpecify
`@Nullable` should be specified after the last `.` with inner or fully qualified types:

- `Cache.@Nullable ValueWrapper`
- `jakarta.validation.@Nullable Validator`

https://jspecify.dev/docs/api/org/jspecify/annotations/NonNull.html[`@NonNull`] and
https://jspecify.dev/docs/api/org/jspecify/annotations/NullUnmarked.html[`@NullUnmarked`] should rarely be needed for
typical use cases.

The {spring-framework-api}/lang/Contract.html[@Contract] annotation in the `org.springframework.lang` package
can be used to express complementary semantics to avoid non-relevant null-safety warnings in your codebase.

NOTE: Complementary to nullability annotations, the {spring-framework-api}/lang/CheckReturnValue.html[@CheckReturnValue]
annotation in the `org.springframework.lang` package can be used to specify that the method return value must be used.

[[null-safety-migrating]]
== Migrating from Spring null-safety annotations

Spring null-safety annotations {spring-framework-api}/lang/Nullable.html[`@Nullable`],
{spring-framework-api}/lang/NonNull.html[`@NonNull`],
{spring-framework-api}/lang/NonNullApi.html[`@NonNullApi`], and
{spring-framework-api}/lang/NonNullFields.html[`@NonNullFields`] in the `org.springframework.lang` package are
deprecated as of Spring Framework 7 and superseded by JSpecify annotations.

A key difference is that Spring null-safety annotations, following JSR 305 semantics, apply to fields,
parameters and return values while JSpecify annotations apply to type usages. This subtle difference
is in practice pretty significant, as it allows for example to differentiate the nullability of elements from the
nullability of arrays/varargs as well as defining the nullability of generic types.

That means array and varargs null-safety declarations have to be updated to keep the same semantic. For example
`@Nullable Object[] array` with Spring annotations needs to be changed to `Object @Nullable [] array` with JSpecify
annotations. Same for varargs.

It is also recommended to move field and return value annotations closer to the type, for example:

- For fields, instead of `@Nullable private String field` with Spring annotations, use `private @Nullable String field`
with JSpecify annotations.
- For return values, instead of `@Nullable public String method()` with Spring annotations, use
`public @Nullable String method()` with JSpecify annotations.

Also, with JSpecify, you don't need to specify `@NonNull` when overriding a type usage annotated with `@Nullable` in the
super method to "undo" the nullable declaration in null-marked code. Just declare it unannotated and the null-marked
defaults (a type usage is considered non-null unless explicitly annotated as nullable) will apply.

It is neither necessary nor recommended to add a JSR-305 dependency to the project classpath to
take advantage of Spring's null-safe APIs. Only projects such as Spring-based libraries that use
null-safety annotations in their codebase should add `com.google.code.findbugs:jsr305:3.0.2`
with `compileOnly` Gradle configuration or Maven `provided` scope to avoid compiler warnings.
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,11 @@ One of Kotlin's key features is {kotlin-docs}/null-safety.html[null-safety],
which cleanly deals with `null` values at compile time rather than bumping into the famous
`NullPointerException` at runtime. This makes applications safer through nullability
declarations and expressing "`value or no value`" semantics without paying the cost of wrappers, such as `Optional`.
(Kotlin allows using functional constructs with nullable values. See this
{baeldung-blog}/kotlin-null-safety[comprehensive guide to Kotlin null-safety].)
Kotlin allows using functional constructs with nullable values. See this
{baeldung-blog}/kotlin-null-safety[comprehensive guide to Kotlin null-safety].

Although Java does not let you express null-safety in its type-system, the Spring Framework
provides xref:languages/kotlin/null-safety.adoc[null-safety of the whole Spring Framework API]
via tooling-friendly annotations declared in the `org.springframework.lang` package.
By default, types from Java APIs used in Kotlin are recognized as
{kotlin-docs}/java-interop.html#null-safety-and-platform-types[platform types],
for which null-checks are relaxed.
{kotlin-docs}/java-interop.html#jsr-305-support[Kotlin support for JSR-305 annotations]
and Spring nullability annotations provide null-safety for the whole Spring Framework API to Kotlin developers,
with the advantage of dealing with `null`-related issues at compile time.

NOTE: Libraries such as Reactor or Spring Data provide null-safe APIs to leverage this feature.

You can configure JSR-305 checks by adding the `-Xjsr305` compiler flag with the following
options: `-Xjsr305={strict|warn|ignore}`.

For kotlin versions 1.1+, the default behavior is the same as `-Xjsr305=warn`.
The `strict` value is required to have Spring Framework API null-safety taken into account
in Kotlin types inferred from Spring API but should be used with the knowledge that Spring
API nullability declaration could evolve even between minor releases and that more checks may
be added in the future.

NOTE: Generic type arguments, varargs, and array elements nullability are not supported yet,
but should be in an upcoming release. See {kotlin-github-org}/KEEP/issues/79[this discussion]
for up-to-date information.



provides xref:core/null-safety.adoc[null-safety of the whole Spring Framework API]
via tooling-friendly https://jspecify.dev/[JSpecify] annotations.

As of Kotlin 2.1, Kotlin enforces strict handling of nullability annotations from `org.jspecify.annotations` package.

0 comments on commit 6d2d10c

Please sign in to comment.