Skip to content

DATACMNS-1190 - Added section on how to enforce nullability constraints on repositories #253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATACMNS-1190-SNAPSHOT</version>

<name>Spring Data Core</name>

Expand Down
4 changes: 2 additions & 2 deletions src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
= Spring Data Commons - Reference Documentation
Oliver Gierke; Thomas Darimont; Christoph Strobl; Mark Pollack; Thomas Risberg;
Oliver Gierke; Thomas Darimont; Christoph Strobl; Mark Pollack; Thomas Risberg; Mark Paluch;
:revnumber: {version}
:revdate: {localdate}
:toc:
:toc-placement!:

(C) 2008-2015 The original authors.
(C) 2008-2017 The original authors.

NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.

Expand Down
160 changes: 96 additions & 64 deletions src/main/asciidoc/repositories.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,102 @@ In this first step you defined a common base interface for all your domain repos
NOTE: Note, that the intermediate repository interface is annotated with `@NoRepositoryBean`. Make sure you add that annotation to all repository interfaces that Spring Data should not create instances for at runtime.


[[repositories.nullability]]
=== Null handling of repository methods

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.
Besides that, Spring Data supports to return other wrapper types on query methods:

* `com.google.common.base.Optional`
* `scala.Option`
* `io.vavr.control.Option`
* `javaslang.control.Option` (deprecated as Javaslang is deprecated)

Alternatively query methods can choose not to use a wrapper type at all.
The absence of a query result will then be indicated by returning `null`.
Repository methods returning collections, wrappers, and streams are guaranteed never to return `null` but rather the corresponding empty representation.

[[repositories.nullability.annotations]]
==== Nullability annotations

You can express null-safe repository methods by using link:{spring-framework-docs}core.html#null-safety[Spring Framework's annotations].
They provide a tooling-friendly approach and opt-in for `null` checks during runtime:

* https://docs.spring.io/spring-framework/docs/{springVersion}/javadoc-api/org/springframework/lang/NonNull.html[`@NonNull`]
annotation where a specific parameter or return value cannot be `null`
(not needed on parameter and return value where `@NonNullApi` applies).

* https://docs.spring.io/spring-framework/docs/{springVersion}/javadoc-api/org/springframework/lang/Nullable.html[`@Nullable`]
annotation where a specific parameter or return value can be `null`.

* https://docs.spring.io/spring-framework/docs/{springVersion}/javadoc-api/org/springframework/lang/NonNullApi.html[`@NonNullApi`]
annotation at package level declares non-null as the default behavior for parameters and return values.

Spring annotations are meta-annotated with https://jcp.org/en/jsr/detail?id=305[JSR 305] annotations (a dormant but widely spread JSR). JSR 305 meta-annotations allow tooling vendors like https://www.jetbrains.com/help/idea/nullable-and-notnull-annotations.html[IDEA], http://help.eclipse.org/oxygen/index.jsp?topic=/org.eclipse.jdt.doc.user/tasks/task-using_external_null_annotations.htm[Eclipse], or link:https://kotlinlang.org/docs/reference/java-interop.html#null-safety-and-platform-types[Kotlin] to 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 package level using Spring’s `@NonNullApi` in `package-info.java`:

.Declaring non-nullability in `package-info.java`
====
[source, java]
----
@org.springframework.lang.NonNullApi
package com.acme;
----
====

Once non-null defaulting is in place, repository query method invocations will get validated at runtime for nullability constraints.
Exceptions will be thrown in case a query execution result violates the defined constraint, i.e. the method would return `null` for some reason but is declared as non-nullable (the default with the annotation defined on the package the repository resides in).
If you want to opt-in to nullable results again, use selectively `@Nullable` on a method.

Using the aforementioned result wrapper types will continue to work as expected, i.e. an empty result will be translated into the value representing absence.

.Using different nullability constraints
====
[source, java]
----
package com.acme;

import org.springframework.lang.Nullable;

interface UserRepository extends Repository<User, Long> {

User getByEmailAddress(EmailAddress emailAddress); <1>

@Nullable
User findByEmailAddess(@Nullable EmailAddress emailAdress); <2>

Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); <3>
}
----
<1> Will throw an `EmptyResultDataAccessException` in case the query executed does not produce a result. Will throw an `IllegalArgumentException` in case the `emailAddress` handed to the method is `null`.
<2> Will return `null` in case the query executed does not produce a result. Also accepts `null` as value for `emailAddress`.
<3> Will return `Optional.empty()` in case the query executed does not produce a result. Will throw an `IllegalArgumentException` in case the `emailAddress` handed to the method is `null`.
====

[[repositories.nullability.kotlin]]
==== Nullability in Kotlin-based repositories

Kotlin has the definition of https://kotlinlang.org/docs/reference/null-safety.html[nullability constraints]
baked into the language.
Kotlin code compiles to bytecode which does not express nullability constraints using method signatures but rather compiled-in metadata. Make sure to include `kotlin-reflect` 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:

.Using nullability constraints on Kotlin repositories
====
[source, kotlin]
----
interface UserRepository : Repository<User, String> {

fun findByUsername(username: String): User <1>

fun findByFirstname(firstname: String?): User? <2>
}
----
<1> The method defines both, the parameter as non-nullable (the Kotlin default) as well as the result. The Kotlin compiler will already reject method invocations trying to hand `null` into the method. In case the query execution yields an empty result, an `EmptyResultDataAccessException` will be thrown.
<2> This method accepts `null` as parameter for `firstname` and returns `null` in case the query execution does not produce a result.
====

[[repositories.multiple-modules]]
=== Using Repositories with multiple Spring Data modules

Expand Down Expand Up @@ -866,70 +962,6 @@ class AnAggregateRoot {

The methods will be called every time one of a Spring Data repository's `save(…)` methods is called.

[[core.nullability-validation]]
== Null-safety

Repository methods let you improve null-safety to deal with `null` values at compile time rather than bumping into the famous `NullPointerException` at runtime. This makes your applications safer through clean nullability declarations, expressing "value or no value" semantics without paying the cost of a wrapper like `Optional`.

You can express null-safe repository methods by using Spring Framework's annotations. They provide a tooling-friendly approach and opt-in for `null` checks during runtime:

* `@NonNullApi` annotations at package level declare non-null as the default behavior

* `@Nullable` annotations where specific parameters or return values can be `null`.

Both annotations are meta-annotated with https://jcp.org/en/jsr/detail?id=305[JSR-305] meta-annotations (a dormant JSR but supported by tools like IDEA, Eclipse, Findbugs, etc.) to provide useful warnings to Java developers.

Make sure to include a JAR file containing JSR-305's `@Nonnull` annotation on your class path if you intend to use own meta-annotations.

NOTE: Invocations of repository query methods in the scope of null-declarations, either declared on package-level or with Kotlin, are validated during runtime. Passing a `null` value to a query method parameter that is not-nullable is rejected with an exception. A query method that yields no result and is not-nullable throws `EmptyResultDataAccessException` instead of returning `null`.

.Activating non-null defaults for a package
====
[source, java]
----
@org.springframework.lang.NonNullApi
package com.example;
----
====

.Declaring nullability for parameters and return values
====
[source, java]
----
package com.example; <1>

interface UserRepository extends Repository<User, String> {

List<User> findByLastname(@Nullable String firstname); <2>

@Nullable
User findByFirstnameAndLastname(String firstname, String lastname); <3>
}
----
<1> `@NonNullApi` on package-level declares that all API within this package
defaults to non-null.
<2> `@Nullable` allows `null` usage on particular parameters. Each nullable parameter
must be annotated.
<3> Methods that may return `null` are annotated with `@Nullable`.
====

If you declare your repository interfaces with Kotlin, then you can use Kotlin's https://kotlinlang.org/docs/reference/null-safety.html[null-safety] to express nullability.

.Declaring nullability for parameters and return values in Kotlin
====
[source, java]
----
interface UserRepository : Repository<User, String> {

fun findByLastname(username: String?): List<User>

fun findByFirstnameAndLastname(firstname: String, lastname: String): User?
}
----
====

NOTE: Kotlin code compiles to bytecode which does not express nullability declarations using method signatures but rather compiled-in metadata. Make sure to include `kotlin-reflect` to enable introspection of Kotlin's nullability declarations.

[[core.extensions]]
== Spring Data extensions

Expand Down