Skip to content

Handling optionality in Spring Data 2.0 code base

Oliver Gierke edited this page Feb 24, 2017 · 2 revisions

Moving the Spring Data (Commons) code base to Java 8 for 2.0 raises the question how we’re going to deal with code that previously used null to indicate the absence of a value. Java 8 ships Optional as a type that sort of plays in that area but it a quite constrained type and its usage is a highly debated topic.

We’ve tried to find a canonical way to decide when to use Optional but coming up with a canonical rule seemed to be impossible as different driving forces might lead to different decisions. We came up with a set of heuristics that

Prefer actual domain types with a value that represents absence.

In general, using Optional should only be considered if there’s no way to represent the absence of a value using the type that’s wrapped. Especially in cases if there’s more domain semantics to the absence than just the absence itself. Take a Sort instance that can be present to define sorting options on e.g. a query method. The absence of that value actually has a meaning: unsorted. Using that concept explicitly leads to way better code as we can expose methods like sort.isSorted() rather than having to use sort.isPresent(). Daniel Westheide actually goes into much more detail about that concept in his blog post here.

Avoid Optional<ConcreteType> in method parameters.

Whenever a method is supposed to be designed to use optional parameters, prefer overloads without the Optional wrapper instead of a single method taking Optionals. So prefer:

someMethod(SomeType type);
someMethod(SomeType type, SomeOtherType other);

over:

someMethod(SomeType type, Optional<SomeOtherType> other);

There’s one exception to this rule that’s driven by two main issues: the type system (see 3) and the values handed around (see 4).

Use Optional if the type of the optional parameter is Object.

The one exception to rule 2 is the case when the optional parameter is of type Object. Assume we got some Optional<Object> as source for this value and a method like this:

someMethod(SomeType type);
someMethod(SomeType type, Object other);

In this case the actually needed client code would have to look like this:

Optional<Object> value = …
value.map(it -> someMethod(type, it)).orElseGet(() -> someMethod(type));

The Optional needs to be resolved before calling the overloads. However, due to Object being a super type of Optional, the following code would still compile:

Optional<Object> value = …
someMethod(type, value);

This will probably create issues within the implementation of someMethod(…) as it’s probably not implemented unwrapping Optionals properly (it shouldn’t actually). That’s why we prefer to use Optional<Object> in cases we deal with an optional value of type Object, as the risk of an undetected missing overload resolution is too high.

The just mentioned scenario usually appears in code that deals with generic values whose type cannot be determined more closely. This is a very common case in the object-to-store mapping subsystem, where we have to deal with user code and will have to assume it could expose nulls to our code. So we try to limit the usage of that workaround to exactly those parts of the codebase.

Clone this wiki locally