From 6d2d10cffe583683ffc9195a918266c4f8b67f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 16 Dec 2024 12:01:25 +0100 Subject: [PATCH] Update null-safety documentation This commit documents using JSpecify instead of the now deprecated Spring null-safety annotations. Closes gh-28797 --- .../modules/ROOT/pages/core/null-safety.adoc | 156 +++++++++++++----- .../pages/languages/kotlin/null-safety.adoc | 33 +--- 2 files changed, 122 insertions(+), 67 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/core/null-safety.adoc b/framework-docs/modules/ROOT/pages/core/null-safety.adoc index 8e2fe8ed42b..9c54cce99b5 100644 --- a/framework-docs/modules/ROOT/pages/core/null-safety.adoc +++ b/framework-docs/modules/ROOT/pages/core/null-safety.adoc @@ -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. diff --git a/framework-docs/modules/ROOT/pages/languages/kotlin/null-safety.adoc b/framework-docs/modules/ROOT/pages/languages/kotlin/null-safety.adoc index 96070d163f4..213a04c9dfa 100644 --- a/framework-docs/modules/ROOT/pages/languages/kotlin/null-safety.adoc +++ b/framework-docs/modules/ROOT/pages/languages/kotlin/null-safety.adoc @@ -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.