There is some controversy going on around the Java community regarding the Optional type introduced by Java 8. It has been around before1 though now it’s official.
Something I hear during daily business lately here and there:
“… never return
null
, return anOptional
object …” +++ “… you won’t throw anException
, you must return anOptional
instance …” +++ “…NullPointerExceptions
belong to the past when you use theOptional
type …”
Am I obliged to use Java 8
’s Optional
type?
The motive which caused me to contribute some thoughts on the discussion was the design of a service
…
Some example
Given some kind of service
which returns a list of items
with attributes such as an id
, a name
, a description
and whatsoever. The id
unambiguously identifying a single unique item
. The id
s are assigned by the service
upon creation of an item
. The service
provides you methods such as getAllItems()
returning a collection with item
objects (having an id
, a name
, a description
and whatsoever).
Now the code fragment in question was the method contract for retrieving a single item
by an unambiguous id
. Amongst others you could consider the below three options on how to design the required functionality:
- Option 1: Return
null
if there is noitem
with such anid
:
- Option 2: Return an
Optional
to be forced to take care of the presence or the absence of theitem
:
- Option 3: Cause an exceptional situation in case there is no
item
for the givenid
:
Whilst having some coding session we came across the options above, in the group there was someone’s definite statement to use the second option Optional<Item> getItem( String id );
as this is the right one whilst Item getItem( String id ) throws NoSuchItemException;
is simply not the way to do it anymore.
Usually I feel uncomfortable with definite statements such as the one above; it’s just a statement without a profound reasoning, there is some smell on it … so I decided to do some research and activate my brain. When searching the web for “java
8
Optional
”, amongst the first results are articles such as “Tired of Null Pointer Exceptions? Consider Using Java SE 8’s Optional!” or “Java 8 Optional: How to Use it”.
Gotten some input and doing some thinking I believe it is simply bad style to use option 2 Optional<Item> getItem( String id );
in the given example above. My favorite approach for the given case is option 3. Item getItem( String id ) throws NoSuchItemException;
. I’ll tell you why:
Reasoning
Let’s take a look at at the example and the semantics behind the desired retrieval operation: I pass an id
to the service
to retrieve the according item
. I have an id
which I did not make up in my mind. I got the id
from somewhere; either I provided it to the service
whilst creating an item
and the service
accepted it without objection. Or upon an according request from my part to the service
, it provided me with a list of items
; each providing its id
attribute.
Given this, I actually expect to get an item
for the provided id
and given that nothing unexpectedly happen to that item
. So the ordinary (intended) execution path of my business logic expects an item
in order to proceed. Not getting an item
would force me to leave the ordinary execution path of my business logic and handle this unusual (unexpected or exceptional) situation. Let’s examine the options introduced before:
Option 1
I might expect an item
as the method’s signature does not draw my attention to the exceptional situation (varying from the ordinary one) of null
being returned when there is no such item
. I might just continue the ordinary execution path even though I got a null
reference. And voilà, there is that NullPointerException
when I apply method calls on that non-existing item
. You could do a null
test before continuing with the ordinary execution path, though then you mix up the exceptional execution path with the ordinary one.
You may run unwarned into a
NullPointerException
. The ordinary and the exceptional execution paths get mixed up: The code gets more complex and its readability (and maintainability) becomes poor and your concerns are not separated2 any more.
Looks to me Option 1 is bad …
Option 2
Now the Optional
draws your attention to the possibility of the absence of an item
. You switch on your brain and feel you have to take some precautions in case of the absence of an item
. You might test for an item
’s presence and leave the ordinary execution path in case of its absence and enter an exceptional execution path to gracefully cope with this situation. This looks much better than returning null
.
The drawback here is similar to the null
test of Option 1: As you test for the presence of an item
before continuing with the ordinary execution path, you mix up the exceptional execution path with the ordinary one.
You do not run unwarned into a
NullPointerException
anymore. The ordinary and the exceptional execution paths still get mixed up: The code gets more complex and its readability (and maintainability) becomes poor and your concerns are still not really separated2.
You can argue that the Optional
allows you to separate your concerns, though Java already provides stronger means to express and separate exceptional situations.
Regarding the method signature, it feels that the presence or the absence of an item
are equally valid options for that method call, although we are pretty sure to expect an item
for our ordinary execution path.
Looks to me Option 2 has a smell …
Option 3
In case there is no item
for the provided id
a NoSuchItemException
is thrown. In such a case you leave the ordinary execution path and enter the exceptional execution path without having to bother with the exceptional situation in your ordinary execution path. The exceptional execution path is cleanly separated from your ordinary execution part (by a catch
block), enhancing readability and not mixing up your concerns. This is a quite strong expressiveness Java offers us.
Your ordinary execution path can concentrate on its sunny day business logic. The exceptional execution path is cleanly separated2 from the ordinary execution path (by a
catch
block) improving readability and maintainability. And finally you are forced to take care of the exceptional execution path without accidentally running into aNullPointerException
.
Option 3 feels like a sound solution to me!
It depends!
“When I had to make some definite statement, I’d say: It depends … a dogmatic right or wrong does not lead to better software - switch on your brains … are you in a service or a data-structure, is it really optional or exceptional what you try to figure out?”
I decided to distinguish between the ordinary execution path and an exceptional execution path of my business logic as well as between services
and data-structures
:
Ordinary execution path
That’s it what you actually want to accomplish with your business logic. As of my opinion, everything which does not belong to your ordinary execution path does not belong inside an Optional
instance.
Exceptional execution path
That’s what might come into your way trying to accomplish the ordinary execution path of your business logic. Once again, everything which does belong to your exceptional execution path does not belong inside an Optional
instance.
Service method
Being the service
a part of my business logic, I use the Optional
type when the presence or the absence of an item
really is optional from the business logic’s point of view. If you expect an item
as of the example before, I solely expect an item
instance and everything else is exceptional (as of Option 3 above). Given you want to retrieve an optional comment for an item
, the method provided by the service
could look as follows:
At a first glance I would be fine with that. Though from a software designer’s point of view this has a smell as well:
Data-structure attribute
Instead of adding a method as such above, I definitely prefer to have an optional comment
attribute in the item
type. So make the comment
attribute in your item
be Optional<String> getComment();
! The id
attribute is definitely not optional, here precautions (assertions) should be taken that id
never can be null
. So it obviously makes sense to distinguish between a service
and a data-structure
.
“Just my 5 cents …”
Some further notes
Since Java 8 there are lambda
expressions (aka closures
). The Optional
provides an ifPresent(Consumer<? super T> consumer)
method which you can feed with a lambda
invoked only in case the Optional
wraps something else than null
. This eases the life of the developer’s daily work. Also by providing a default value in case the Optional
contains just a null
reference via the orElse(T other)
method makes life easier.
Facilitate the power of functional programming with the
Optional
class.
Interesting to consider is that the Optional
is not Serializable
making it useless whenever you propagate a value by serialization throughout JVM
borders. This happens quite often when using REST
or SOAP
based communication. Here it is up to the underlying framework to take care of proper handling.
Having done some research before writing this article I stumbled over some inspiring texts:
“… An immutable object that may contain a non-null reference to another object. Each instance of this type either contains a non-null reference, or contains nothing (in which case we say that the reference is “absent”); it is never said to “contain null” …”1
“… design principle for separating a computer program into distinct sections, such that each section addresses a separate concern …”2
“… You should use
Optionals
in your data model: to leverage the type system to clearly differentiate the values that can be missing (the car in the Person class and the insurance in the Car class in my example) from the ones that must be there (the name of the insurance company). In this way you know, through the type system, that a person without a car is acceptable in your data model while an insurance company without a name isn’t …”3
“… Optional is an attempt to reduce the number of null pointer exceptions in Java systems, by adding the possibility to build more expressive APIs that account for the possibility that sometimes return values are missing …”4
“… String version = computer.getSoundcard().getUSB().getVersion(); … A common (bad) practice is to return the null reference to indicate the absence of a sound card. Unfortunately, this means the call to getUSB() will try to return the USB port of a null reference, which will result in a NullPointerException at runtime and stop your program from running further …”5