-
Notifications
You must be signed in to change notification settings - Fork 674
Handling optionality in Spring Data 2.0 code base
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
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.
Whenever a method is supposed to be designed to use optional parameters, prefer overloads without the Optional
wrapper instead of a single method taking Optional
s.
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).
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 Optional
s 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 null
s to our code.
So we try to limit the usage of that workaround to exactly those parts of the codebase.