|
1 | 1 | [[null-safety]]
|
2 | 2 | = Null-safety
|
3 | 3 |
|
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. |
7 | 8 |
|
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]. |
17 | 14 |
|
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`]. |
24 | 17 |
|
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 |
28 | 20 |
|
| 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. |
29 | 26 |
|
| 27 | +[[null-safety-applications]] |
| 28 | +== Leveraging JSpecify annotations in Spring applications |
30 | 29 |
|
| 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. |
31 | 33 |
|
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. |
34 | 36 |
|
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 |
38 | 39 |
|
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. |
42 | 42 |
|
| 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: |
43 | 48 |
|
| 49 | +[source,java,subs="verbatim,quotes",chomp="-packages",fold="none"] |
| 50 | +---- |
| 51 | +@NullMarked |
| 52 | +package org.springframework.core; |
44 | 53 |
|
| 54 | +import org.jspecify.annotations.NullMarked; |
| 55 | +---- |
45 | 56 |
|
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. |
48 | 60 |
|
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. |
53 | 135 |
|
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. |
|
0 commit comments