|
| 1 | +[[repositories.nullability]] |
| 2 | +=== Null Handling of Repository Methods |
| 3 | + |
| 4 | +As of Spring Data 2.0, repository CRUD methods that return an individual aggregate instance use Java 8's `Optional` to indicate the potential absence of a value. |
| 5 | +Besides that, Spring Data supports returning the following wrapper types on query methods: |
| 6 | + |
| 7 | +* `com.google.common.base.Optional` |
| 8 | +* `scala.Option` |
| 9 | +* `io.vavr.control.Option` |
| 10 | + |
| 11 | +Alternatively, query methods can choose not to use a wrapper type at all. |
| 12 | +The absence of a query result is then indicated by returning `null`. |
| 13 | +Repository methods returning collections, collection alternatives, wrappers, and streams are guaranteed never to return `null` but rather the corresponding empty representation. |
| 14 | +See "`<<repository-query-return-types>>`" for details. |
| 15 | + |
| 16 | +[[repositories.nullability.annotations]] |
| 17 | +==== Nullability Annotations |
| 18 | + |
| 19 | +You can express nullability constraints for repository methods by using {spring-framework-docs}/core.html#null-safety[Spring Framework's nullability annotations]. |
| 20 | +They provide a tooling-friendly approach and opt-in `null` checks during runtime, as follows: |
| 21 | + |
| 22 | +* {spring-framework-javadoc}/org/springframework/lang/NonNullApi.html[`@NonNullApi`]: Used on the package level to declare that the default behavior for parameters and return values is, respectively, neither to accept nor to produce `null` values. |
| 23 | +* {spring-framework-javadoc}/org/springframework/lang/NonNull.html[`@NonNull`]: Used on a parameter or return value that must not be `null` (not needed on a parameter and return value where `@NonNullApi` applies). |
| 24 | +* {spring-framework-javadoc}/org/springframework/lang/Nullable.html[`@Nullable`]: Used on a parameter or return value that can be `null`. |
| 25 | + |
| 26 | +Spring annotations are meta-annotated with https://jcp.org/en/jsr/detail?id=305[JSR 305] annotations (a dormant but widely used JSR). |
| 27 | +JSR 305 meta-annotations let tooling vendors (such as https://www.jetbrains.com/help/idea/nullable-and-notnull-annotations.html[IDEA], https://help.eclipse.org/latest/index.jsp?topic=/org.eclipse.jdt.doc.user/tasks/task-using_external_null_annotations.htm[Eclipse], and link:https://kotlinlang.org/docs/reference/java-interop.html#null-safety-and-platform-types[Kotlin]) provide null-safety support in a generic way, without having to hard-code support for Spring annotations. |
| 28 | +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: |
| 29 | + |
| 30 | +.Declaring Non-nullability in `package-info.java` |
| 31 | +==== |
| 32 | +[source,java] |
| 33 | +---- |
| 34 | +@org.springframework.lang.NonNullApi |
| 35 | +package com.acme; |
| 36 | +---- |
| 37 | +==== |
| 38 | + |
| 39 | +Once non-null defaulting is in place, repository query method invocations get validated at runtime for nullability constraints. |
| 40 | +If a query result violates the defined constraint, an exception is thrown. |
| 41 | +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). |
| 42 | +If you want to opt-in to nullable results again, selectively use `@Nullable` on individual methods. |
| 43 | +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. |
| 44 | + |
| 45 | +The following example shows a number of the techniques just described: |
| 46 | + |
| 47 | +.Using different nullability constraints |
| 48 | +==== |
| 49 | +[source,java] |
| 50 | +---- |
| 51 | +package com.acme; <1> |
| 52 | +
|
| 53 | +import org.springframework.lang.Nullable; |
| 54 | +
|
| 55 | +interface UserRepository extends Repository<User, Long> { |
| 56 | +
|
| 57 | + User getByEmailAddress(EmailAddress emailAddress); <2> |
| 58 | +
|
| 59 | + @Nullable |
| 60 | + User findByEmailAddress(@Nullable EmailAddress emailAdress); <3> |
| 61 | +
|
| 62 | + Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); <4> |
| 63 | +} |
| 64 | +---- |
| 65 | +<1> The repository resides in a package (or sub-package) for which we have defined non-null behavior. |
| 66 | +<2> Throws an `EmptyResultDataAccessException` when the query does not produce a result. |
| 67 | +Throws an `IllegalArgumentException` when the `emailAddress` handed to the method is `null`. |
| 68 | +<3> Returns `null` when the query does not produce a result. |
| 69 | +Also accepts `null` as the value for `emailAddress`. |
| 70 | +<4> Returns `Optional.empty()` when the query does not produce a result. |
| 71 | +Throws an `IllegalArgumentException` when the `emailAddress` handed to the method is `null`. |
| 72 | +==== |
| 73 | + |
| 74 | +[[repositories.nullability.kotlin]] |
| 75 | +==== Nullability in Kotlin-based Repositories |
| 76 | + |
| 77 | +Kotlin has the definition of https://kotlinlang.org/docs/reference/null-safety.html[nullability constraints] baked into the language. |
| 78 | +Kotlin code compiles to bytecode, which does not express nullability constraints through method signatures but rather through compiled-in metadata. |
| 79 | +Make sure to include the `kotlin-reflect` JAR in your project to enable introspection of Kotlin's nullability constraints. |
| 80 | +Spring Data repositories use the language mechanism to define those constraints to apply the same runtime checks, as follows: |
| 81 | + |
| 82 | +.Using nullability constraints on Kotlin repositories |
| 83 | +==== |
| 84 | +[source,kotlin] |
| 85 | +---- |
| 86 | +interface UserRepository : Repository<User, String> { |
| 87 | +
|
| 88 | + fun findByUsername(username: String): User <1> |
| 89 | +
|
| 90 | + fun findByFirstname(firstname: String?): User? <2> |
| 91 | +} |
| 92 | +---- |
| 93 | +<1> The method defines both the parameter and the result as non-nullable (the Kotlin default). |
| 94 | +The Kotlin compiler rejects method invocations that pass `null` to the method. |
| 95 | +If the query yields an empty result, an `EmptyResultDataAccessException` is thrown. |
| 96 | +<2> This method accepts `null` for the `firstname` parameter and returns `null` if the query does not produce a result. |
| 97 | +==== |
0 commit comments