This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Data Relational 3.4.2! |
Null Handling of Repository Methods
Repository CRUD methods that return an individual aggregate instances can use Optional
to indicate the potential absence of a value.
Besides that, Spring Data supports returning the following wrapper types on query methods:
-
com.google.common.base.Optional
-
scala.Option
-
io.vavr.control.Option
Alternatively, query methods can choose not to use a wrapper type at all.
The absence of a query result is then indicated by returning null
.
Repository methods returning collections, collection alternatives, wrappers, and streams are guaranteed never to return null
but rather the corresponding empty representation.
See “Repository query return types” for details.
Nullability Annotations
JSpecify
As on Spring Framework 7 and Spring Data 4, you can express nullability constraints for repository methods by using JSpecify.
JSpecify is well integrated into IntelliJ and Eclipse to provide a tooling-friendly approach and opt-in null
checks during runtime, as follows:
-
@NullMarked
: Used on the module-, package- and class-level to declare that the default behavior for parameters and return values is, respectively, neither to accept nor to producenull
values. -
@NonNull
: Used on a type level for parameter or return values that must not benull
(not needed value where@NullMarked
applies). -
@Nullable
: Used on the type level for parameter or return values that can benull
. -
@NullUnmarked
: Used on the package-, class-, and method-level to roll back nullness declaration and opt-out from a previous@NullMarked
. Nullness changes to unspecified in such a case.
@NullMarked
at the package level via a package-info.java
file@NullMarked
package org.springframework.core;
import org.jspecify.annotations.NullMarked;
In the various Java files belonging to the package, nullable type usages are defined explicitly with
@Nullable
.
It is recommended that this annotation is specified just before the related type.
For example, for a field:
private @Nullable String fileEncoding;
Or for method parameters and return value:
public static @Nullable String buildMessage(@Nullable String message,
@Nullable Throwable cause) {
// ...
}
When overriding a method, nullness annotations are not inherited from the superclass method. That means those nullness annotations should be repeated if you just want to override the implementation and keep the same API nullness.
With arrays and varargs, you need to be able to differentiate the nullness of the elements from the nullness of the array itself. Pay attention to the syntax 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
@NonNull
and
@NullUnmarked
should rarely be needed for typical use cases.
Spring Framework Nullability and JSR-305 Annotations
You can express nullability constraints for repository methods by using Spring Framework’s nullability annotations.
As on Spring Framework 7, Spring’s nullability annotations are deprecated in favor of JSpecify. Consult the framework documentation on Migrating from Spring null-safety annotations to JSpecify for more information. |
They provide a tooling-friendly approach and opt-in null
checks during runtime, as follows:
-
@NonNullApi
: Used on the package level to declare that the default behavior for parameters and return values is, respectively, neither to accept nor to producenull
values. -
@NonNull
: Used on a parameter or return value that must not benull
(not needed on a parameter and return value where@NonNullApi
applies). -
@Nullable
: Used on a parameter or return value that can benull
.
Spring annotations are meta-annotated with JSR 305 annotations (a dormant but widely used JSR).
JSR 305 meta-annotations let tooling vendors (such as IDEA, Eclipse, and Kotlin) provide null-safety support in a generic way, without having to hard-code support for Spring annotations.
To enable runtime checking of nullability constraints for query methods, you need to activate non-nullability on the package level by using Spring’s @NonNullApi
in package-info.java
, as shown in the following example:
package-info.java
@org.springframework.lang.NonNullApi
package com.acme;
Once non-null defaulting is in place, repository query method invocations get validated at runtime for nullability constraints.
If a query result violates the defined constraint, an exception is thrown.
This happens when the method would return null
but is declared as non-nullable (the default with the annotation defined on the package in which the repository resides).
If you want to opt-in to nullable results again, selectively use @Nullable
on individual methods.
Using the result wrapper types mentioned at the start of this section continues to work as expected: an empty result is translated into the value that represents absence.
The following example shows a number of the techniques just described:
package com.acme; (1)
import org.springframework.lang.Nullable;
interface UserRepository extends Repository<User, Long> {
User getByEmailAddress(EmailAddress emailAddress); (2)
@Nullable
User findByEmailAddress(@Nullable EmailAddress emailAdress); (3)
Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); (4)
}
1 | The repository resides in a package (or sub-package) for which we have defined non-null behavior. |
2 | Throws an EmptyResultDataAccessException when the query does not produce a result.
Throws an IllegalArgumentException when the emailAddress handed to the method is null . |
3 | Returns null when the query does not produce a result.
Also accepts null as the value for emailAddress . |
4 | Returns Optional.empty() when the query does not produce a result.
Throws an IllegalArgumentException when the emailAddress handed to the method is null . |
Nullability in Kotlin-based Repositories
Kotlin has the definition of nullability constraints baked into the language.
Kotlin code compiles to bytecode, which does not express nullability constraints through method signatures but rather through compiled-in metadata.
Make sure to include the kotlin-reflect
JAR in your project to enable introspection of Kotlin’s nullability constraints.
Spring Data repositories use the language mechanism to define those constraints to apply the same runtime checks, as follows:
interface UserRepository : Repository<User, String> {
fun findByUsername(username: String): User (1)
fun findByFirstname(firstname: String?): User? (2)
}
1 | The method defines both the parameter and the result as non-nullable (the Kotlin default).
The Kotlin compiler rejects method invocations that pass null to the method.
If the query yields an empty result, an EmptyResultDataAccessException is thrown. |
2 | This method accepts null for the firstname parameter and returns null if the query does not produce a result. |