Skip to content

Commit 941b791

Browse files
committed
Polish null-safety documentation
1 parent 9782dfb commit 941b791

File tree

1 file changed

+57
-53
lines changed

1 file changed

+57
-53
lines changed

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

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,54 @@
33

44
Although Java does not let you express nullness markers with its type system yet, the Spring Framework codebase is
55
annotated with https://jspecify.dev/docs/start-here/[JSpecify] annotations to declare the nullability of its APIs,
6-
fields and related type usages. Reading the https://jspecify.dev/docs/user-guide/[JSpecify user guide] is highly
6+
fields, and related type usages. Reading the https://jspecify.dev/docs/user-guide/[JSpecify user guide] is highly
77
recommended in order to get familiar with those annotations and semantics.
88

9-
The primary goal of this null-safety arrangement is to prevent `NullPointerException` to be thrown at runtime via build
10-
time checks and to turn explicit nullability into a way to express the possible absence of value. It is useful in both
9+
The primary goal of this null-safety arrangement is to prevent a `NullPointerException` from being thrown at runtime via build
10+
time checks and to use explicit nullability as a way to express the possible absence of value. It is useful in both
1111
Java by leveraging some tooling (https://github.com/uber/NullAway[NullAway] or IDEs supporting JSpecify annotations
12-
such as IntelliJ IDEA for example) and Kotlin where JSpecify annotations are automatically translated to
12+
such as IntelliJ IDEA) and Kotlin where JSpecify annotations are automatically translated to
1313
{kotlin-docs}/null-safety.html[Kotlin's null safety].
1414

1515
The {spring-framework-api}/core/Nullness.html[`Nullness` Spring API] can be used at runtime to detect the nullness of a
16-
type usage, a field, a method return type or a parameter. It provides full support for JSpecify annotations,
17-
Kotlin null safety, Java primitive types, as well as a pragmatic check on any `@Nullable` annotation (regardless of the
16+
type usage, a field, a method return type, or a parameter. It provides full support for JSpecify annotations,
17+
Kotlin null safety, and Java primitive types, as well as a pragmatic check on any `@Nullable` annotation (regardless of the
1818
package).
1919

2020
[[null-safety-libraries]]
2121
== Annotating libraries with JSpecify annotations
2222

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

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

32-
Developing applications with IDEs supporting nullness annotations will provide warnings in Java and errors in Kotlin
32+
Developing applications with IDEs that support nullness annotations will provide warnings in Java and errors in Kotlin
3333
when the nullability contracts are not honored, allowing Spring application developers to refine their null handling to
34-
prevent `NullPointerException` to be thrown at runtime.
34+
prevent a `NullPointerException` from being thrown at runtime.
3535

3636
Optionally, Spring application developers can annotate their codebase and use build plugins like
37-
https://github.com/uber/NullAway[NullAway] to enforce null-safety during build time at application level.
37+
https://github.com/uber/NullAway[NullAway] to enforce null-safety at the application level during build time.
3838

3939
[[null-safety-guidelines]]
4040
== Guidelines
4141

42-
The purpose of this section is to share some guidelines proposed for specifying explicitly the nullability of
42+
The purpose of this section is to share some proposed guidelines for explicitly specifying the nullability of
4343
Spring-related libraries or applications.
4444

4545

4646
[[null-safety-guidelines-jspecify]]
4747
=== JSpecify
4848

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

5555
[source,java,subs="verbatim,quotes",chomp="-packages",fold="none"]
5656
----
@@ -60,7 +60,7 @@ package org.springframework.core;
6060
import org.jspecify.annotations.NullMarked;
6161
----
6262

63-
In the various Java files belonging to the package, nullable type usages are defined explicitly with
63+
In the various Java files belonging to the package, nullable type usage is defined explicitly with
6464
https://jspecify.dev/docs/api/org/jspecify/annotations/Nullable.html[`@Nullable`]. It is recommended that this
6565
annotation is specified just before the related type on the same line.
6666

@@ -71,7 +71,7 @@ For example, for a field:
7171
private @Nullable String fileEncoding;
7272
----
7373

74-
Or for method parameters and return value:
74+
Or for method parameters and method return types:
7575

7676
[source,java,subs="verbatim,quotes"]
7777
----
@@ -81,19 +81,23 @@ public static @Nullable String buildMessage(@Nullable String message,
8181
}
8282
----
8383

84-
When overriding a method, JSpecify annotations are not inherited from the superclass method. That means they should be
85-
repeated if you just want to override the implementation and keep the same nullability.
84+
[NOTE]
85+
====
86+
When overriding a method, JSpecify annotations are not inherited from the original
87+
method. That means the JSpecify annotations should be copied to the overriding method if
88+
you want to override the implementation and keep the same nullability semantics.
89+
====
8690

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

92-
- `@Nullable Object[] array` means individual elements can be null but the array itself can't.
93-
- `Object @Nullable [] array` means individual elements can't be null but the array itself can.
96+
- `@Nullable Object[] array` means individual elements can be null but the array itself cannot.
97+
- `Object @Nullable [] array` means individual elements cannot be null but the array itself can.
9498
- `@Nullable Object @Nullable [] array` means both individual elements and the array can be null.
9599

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

99103
- `Cache.@Nullable ValueWrapper`
@@ -113,39 +117,39 @@ The recommended configuration is:
113117
- `NullAway:OnlyNullMarked=true` in order to perform nullability checks only for packages annotated with `@NullMarked`.
114118
- `NullAway:CustomContractAnnotations=org.springframework.lang.Contract` which makes NullAway aware of the
115119
{spring-framework-api}/lang/Contract.html[@Contract] annotation in the `org.springframework.lang` package which
116-
can be used to express complementary semantics to avoid non-relevant warnings in your codebase.
120+
can be used to express complementary semantics to avoid irrelevant warnings in your codebase.
117121

118-
A good example of `@Contract` benefits is
119-
{spring-framework-api}/util/Assert.html#notNull(java.lang.Object,java.lang.String)[`Assert#notnull`] which is annotated
120-
with `@Contract("null, _ -> fail")`. With the configuration above, NullAway will understand that after a successful
121-
invocation, the value passed as a parameter is not null.
122+
A good example of the benefits of a `@Contract` declaration can be seen with
123+
{spring-framework-api}/util/Assert.html#notNull(java.lang.Object,java.lang.String)[`Assert.notNull()`] which is annotated
124+
with `@Contract("null, _ -> fail")`. With that contract declaration, NullAway will understand that the value passed as a
125+
parameter cannot be null after a successful invocation of `Assert.notNull()`.
122126

123127
Optionally, it is possible to set `NullAway:JSpecifyMode=true` to enable
124128
https://github.com/uber/NullAway/wiki/JSpecify-Support[checks on the full JSpecify semantics], including annotations on
125129
generic types. Be aware that this mode is
126130
https://github.com/uber/NullAway/issues?q=is%3Aissue+is%3Aopen+label%3Ajspecify[still under development] and requires
127131
using JDK 22 or later (typically combined with the `--release` Java compiler flag to configure the
128132
expected baseline). It is recommended to enable the JSpecify mode only as a second step, after making sure the codebase
129-
generates no warning with the recommended configuration mentioned above.
133+
generates no warning with the recommended configuration mentioned previously in this section.
130134

131135
==== Warnings suppression
132136

133-
There are a few valid use cases where NullAway will wrongly detect nullability problems. In such case, it is recommended
137+
There are a few valid use cases where NullAway will incorrectly detect nullability problems. In such case, it is recommended
134138
to suppress related warnings and to document the reason:
135139

136-
- `@SuppressWarnings("NullAway.Init")` at field, constructor or class level can be used to avoid unnecessary warnings
137-
due to the lazy initialization of fields, for example due to a class implementing
140+
- `@SuppressWarnings("NullAway.Init")` at field, constructor, or class level can be used to avoid unnecessary warnings
141+
due to the lazy initialization of fieldsfor example, due to a class implementing
138142
{spring-framework-api}/beans/factory/InitializingBean.html[`InitializingBean`].
139143
- `@SuppressWarnings("NullAway") // Dataflow analysis limitation` can be used when NullAway dataflow analysis is not
140144
able to detect that the path involving a nullability problem will never happen.
141145
- `@SuppressWarnings("NullAway") // Lambda` can be used when NullAway does not take into account assertions performed
142146
outside of a lambda for the code path within the lambda.
143-
- `@SuppressWarnings("NullAway") // Reflection` can be used for some reflection operations that are known returning
144-
non-null values even if that can't be expressed by the API.
145-
- `@SuppressWarnings("NullAway") // Well-known map keys` can be used when `Map#get` invocations are done with keys known
146-
to be present and non-null related values inserted previously.
147-
- `@SuppressWarnings("NullAway") // Overridden method does not define nullability` can be used when the super class does
148-
not define nullability (typically when the super class is coming from a dependency).
147+
- `@SuppressWarnings("NullAway") // Reflection` can be used for some reflection operations that are known to return
148+
non-null values even if that cannot be expressed by the API.
149+
- `@SuppressWarnings("NullAway") // Well-known map keys` can be used when `Map#get` invocations are performed with keys that are known
150+
to be present and when non-null related values have been inserted previously.
151+
- `@SuppressWarnings("NullAway") // Overridden method does not define nullability` can be used when the superclass does
152+
not define nullability (typically when the superclass comes from a dependency).
149153

150154

151155
[[null-safety-migrating]]
@@ -154,30 +158,30 @@ not define nullability (typically when the super class is coming from a dependen
154158
Spring null-safety annotations {spring-framework-api}/lang/Nullable.html[`@Nullable`],
155159
{spring-framework-api}/lang/NonNull.html[`@NonNull`],
156160
{spring-framework-api}/lang/NonNullApi.html[`@NonNullApi`], and
157-
{spring-framework-api}/lang/NonNullFields.html[`@NonNullFields`] in the `org.springframework.lang` package have been
158-
introduced in Spring Framework 5 when JSpecify did not exist and the best option was to leverage JSR 305 (a dormant
159-
but widespread JSR) meta-annotations. They are deprecated as of Spring Framework 7 in favor of
161+
{spring-framework-api}/lang/NonNullFields.html[`@NonNullFields`] in the `org.springframework.lang` package were
162+
introduced in Spring Framework 5 when JSpecify did not exist, and the best option at that time was to leverage
163+
meta-annotations from JSR 305 (a dormant but widespread JSR). They are deprecated as of Spring Framework 7 in favor of
160164
https://jspecify.dev/docs/start-here/[JSpecify] annotations, which provide significant enhancements such as properly
161-
defined specifications, a canonical dependency with no split-package issue, better tooling, better Kotlin integration
162-
and the capability to specify the nullability more precisely for more use cases.
165+
defined specifications, a canonical dependency with no split-package issues, better tooling, better Kotlin integration,
166+
and the capability to specify nullability more precisely for more use cases.
163167

164-
A key difference is that Spring deprecated null-safety annotations, following JSR 305 semantics, apply to fields,
165-
parameters and return values while JSpecify annotations apply to type usages. This subtle difference
166-
is in practice pretty significant, as it allows for example to differentiate the nullness of elements from the
167-
nullness of arrays/varargs as well as defining the nullness of generic types.
168+
A key difference is that Spring's deprecated null-safety annotations, which follow JSR 305 semantics, apply to fields,
169+
parameters, and return values; while JSpecify annotations apply to type usage. This subtle difference
170+
is in practice pretty significant, as it allows developers to differentiate between the nullness of elements and the
171+
nullness of arrays/varargs as well as to define the nullness of generic types.
168172

169-
That means array and varargs null-safety declarations have to be updated to keep the same semantic. For example
173+
That means array and varargs null-safety declarations have to be updated to keep the same semantics. For example
170174
`@Nullable Object[] array` with Spring annotations needs to be changed to `Object @Nullable [] array` with JSpecify
171-
annotations. Same for varargs.
175+
annotations. The same applies to varargs.
172176

173-
It is also recommended to move field and return value annotations closer to the type on the same line, for example:
177+
It is also recommended to move field and return value annotations closer to the type and on the same line, for example:
174178

175179
- For fields, instead of `@Nullable private String field` with Spring annotations, use `private @Nullable String field`
176180
with JSpecify annotations.
177-
- For return values, instead of `@Nullable public String method()` with Spring annotations, use
181+
- For method return types, instead of `@Nullable public String method()` with Spring annotations, use
178182
`public @Nullable String method()` with JSpecify annotations.
179183

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

0 commit comments

Comments
 (0)