Skip to content

Commit 6d2d10c

Browse files
committed
Update null-safety documentation
This commit documents using JSpecify instead of the now deprecated Spring null-safety annotations. Closes gh-28797
1 parent b358656 commit 6d2d10c

File tree

2 files changed

+122
-67
lines changed

2 files changed

+122
-67
lines changed

framework-docs/modules/ROOT/pages/core/null-safety.adoc

Lines changed: 117 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,135 @@
11
[[null-safety]]
22
= Null-safety
33

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

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

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

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

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

27+
[[null-safety-applications]]
28+
== Leveraging JSpecify annotations in Spring applications
3029

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

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

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

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

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

49+
[source,java,subs="verbatim,quotes",chomp="-packages",fold="none"]
50+
----
51+
@NullMarked
52+
package org.springframework.core;
4453
54+
import org.jspecify.annotations.NullMarked;
55+
----
4556

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

49-
Spring annotations are meta-annotated with {JSR}305[JSR 305]
50-
annotations (a dormant but widespread JSR). JSR-305 meta-annotations let tooling vendors
51-
like IDEA or Kotlin provide null-safety support in a generic way, without having to
52-
hard-code support for Spring annotations.
61+
For example, for a field:
62+
63+
[source,java,subs="verbatim,quotes"]
64+
----
65+
private @Nullable String fileEncoding;
66+
----
67+
68+
Or for method parameters and return value:
69+
70+
[source,java,subs="verbatim,quotes"]
71+
----
72+
public static @Nullable String buildMessage(@Nullable String message,
73+
@Nullable Throwable cause) {
74+
// ...
75+
}
76+
----
77+
78+
When overriding a method, nullability annotations are not inherited from the superclass method. That means those
79+
nullability annotations should be repeated if you just want to override the implementation and keep the same API
80+
nullability.
81+
82+
With arrays and varargs, you need to be able to differentiate the nullability of the elements from the nullability of
83+
the array itself. Pay attention to the syntax
84+
https://docs.oracle.com/javase/specs/jls/se17/html/jls-9.html#jls-9.7.4[defined by the Java specification] which may be
85+
initially surprising:
86+
87+
- `@Nullable Object[] array` means individual elements can be null but the array itself can't.
88+
- `Object @Nullable [] array` means individual elements can't be null but the array itself can.
89+
- `@Nullable Object @Nullable [] array` means both individual elements and the array can be null.
90+
91+
The Java specifications also enforces that annotations defined with `@Target(ElementType.TYPE_USE)` like JSpecify
92+
`@Nullable` should be specified after the last `.` with inner or fully qualified types:
93+
94+
- `Cache.@Nullable ValueWrapper`
95+
- `jakarta.validation.@Nullable Validator`
96+
97+
https://jspecify.dev/docs/api/org/jspecify/annotations/NonNull.html[`@NonNull`] and
98+
https://jspecify.dev/docs/api/org/jspecify/annotations/NullUnmarked.html[`@NullUnmarked`] should rarely be needed for
99+
typical use cases.
100+
101+
The {spring-framework-api}/lang/Contract.html[@Contract] annotation in the `org.springframework.lang` package
102+
can be used to express complementary semantics to avoid non-relevant null-safety warnings in your codebase.
103+
104+
NOTE: Complementary to nullability annotations, the {spring-framework-api}/lang/CheckReturnValue.html[@CheckReturnValue]
105+
annotation in the `org.springframework.lang` package can be used to specify that the method return value must be used.
106+
107+
[[null-safety-migrating]]
108+
== Migrating from Spring null-safety annotations
109+
110+
Spring null-safety annotations {spring-framework-api}/lang/Nullable.html[`@Nullable`],
111+
{spring-framework-api}/lang/NonNull.html[`@NonNull`],
112+
{spring-framework-api}/lang/NonNullApi.html[`@NonNullApi`], and
113+
{spring-framework-api}/lang/NonNullFields.html[`@NonNullFields`] in the `org.springframework.lang` package are
114+
deprecated as of Spring Framework 7 and superseded by JSpecify annotations.
115+
116+
A key difference is that Spring null-safety annotations, following JSR 305 semantics, apply to fields,
117+
parameters and return values while JSpecify annotations apply to type usages. This subtle difference
118+
is in practice pretty significant, as it allows for example to differentiate the nullability of elements from the
119+
nullability of arrays/varargs as well as defining the nullability of generic types.
120+
121+
That means array and varargs null-safety declarations have to be updated to keep the same semantic. For example
122+
`@Nullable Object[] array` with Spring annotations needs to be changed to `Object @Nullable [] array` with JSpecify
123+
annotations. Same for varargs.
124+
125+
It is also recommended to move field and return value annotations closer to the type, for example:
126+
127+
- For fields, instead of `@Nullable private String field` with Spring annotations, use `private @Nullable String field`
128+
with JSpecify annotations.
129+
- For return values, instead of `@Nullable public String method()` with Spring annotations, use
130+
`public @Nullable String method()` with JSpecify annotations.
131+
132+
Also, with JSpecify, you don't need to specify `@NonNull` when overriding a type usage annotated with `@Nullable` in the
133+
super method to "undo" the nullable declaration in null-marked code. Just declare it unannotated and the null-marked
134+
defaults (a type usage is considered non-null unless explicitly annotated as nullable) will apply.
53135

54-
It is neither necessary nor recommended to add a JSR-305 dependency to the project classpath to
55-
take advantage of Spring's null-safe APIs. Only projects such as Spring-based libraries that use
56-
null-safety annotations in their codebase should add `com.google.code.findbugs:jsr305:3.0.2`
57-
with `compileOnly` Gradle configuration or Maven `provided` scope to avoid compiler warnings.

framework-docs/modules/ROOT/pages/languages/kotlin/null-safety.adoc

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,11 @@ One of Kotlin's key features is {kotlin-docs}/null-safety.html[null-safety],
55
which cleanly deals with `null` values at compile time rather than bumping into the famous
66
`NullPointerException` at runtime. This makes applications safer through nullability
77
declarations and expressing "`value or no value`" semantics without paying the cost of wrappers, such as `Optional`.
8-
(Kotlin allows using functional constructs with nullable values. See this
9-
{baeldung-blog}/kotlin-null-safety[comprehensive guide to Kotlin null-safety].)
8+
Kotlin allows using functional constructs with nullable values. See this
9+
{baeldung-blog}/kotlin-null-safety[comprehensive guide to Kotlin null-safety].
1010

1111
Although Java does not let you express null-safety in its type-system, the Spring Framework
12-
provides xref:languages/kotlin/null-safety.adoc[null-safety of the whole Spring Framework API]
13-
via tooling-friendly annotations declared in the `org.springframework.lang` package.
14-
By default, types from Java APIs used in Kotlin are recognized as
15-
{kotlin-docs}/java-interop.html#null-safety-and-platform-types[platform types],
16-
for which null-checks are relaxed.
17-
{kotlin-docs}/java-interop.html#jsr-305-support[Kotlin support for JSR-305 annotations]
18-
and Spring nullability annotations provide null-safety for the whole Spring Framework API to Kotlin developers,
19-
with the advantage of dealing with `null`-related issues at compile time.
20-
21-
NOTE: Libraries such as Reactor or Spring Data provide null-safe APIs to leverage this feature.
22-
23-
You can configure JSR-305 checks by adding the `-Xjsr305` compiler flag with the following
24-
options: `-Xjsr305={strict|warn|ignore}`.
25-
26-
For kotlin versions 1.1+, the default behavior is the same as `-Xjsr305=warn`.
27-
The `strict` value is required to have Spring Framework API null-safety taken into account
28-
in Kotlin types inferred from Spring API but should be used with the knowledge that Spring
29-
API nullability declaration could evolve even between minor releases and that more checks may
30-
be added in the future.
31-
32-
NOTE: Generic type arguments, varargs, and array elements nullability are not supported yet,
33-
but should be in an upcoming release. See {kotlin-github-org}/KEEP/issues/79[this discussion]
34-
for up-to-date information.
35-
36-
37-
12+
provides xref:core/null-safety.adoc[null-safety of the whole Spring Framework API]
13+
via tooling-friendly https://jspecify.dev/[JSpecify] annotations.
3814

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

0 commit comments

Comments
 (0)