I used to see Optional<Something> as a completely useless addition to Java, adding to the bloat of Java without being any clearer than checking if a reference is null.
I have changed my mind, and now think Optional<> can be useful in making code simpler, easier to understand, and more robust.
The reason I changed my mind, was the usefulness of Optional.orElseThrow() when having to traverse deeply nested data structures in Java code.
A lot of Java code has to deal with complex and hierarchical data structures.
From the Point of View of a programmer, hierarchical data structures are good for one thing, which is populating a GUI treeview.
When You have a hierarchical data structure, it is often possible to create a professional looking GUI to explore hierarchical data, using very little code.
But when what you have is a hiearchical data structure, and what you want, is a value deep down in a leaf node in the hierarchy, and when the leaf node may or may not be present, and the same goes for the intermediate nodes in the data structure, then hierarchical data structures becomes a hassle.
Accessing nested values, without considering the possibility of missing values at some level in the structure, like in this example, are NullPointerExceptions waiting to happen:
Integer leafnodeValue = someService.findSomething().findOther().findNestedOther().someValue();
NullPointerExceptions are the most useless exceptions that can be thrown. NPEs have no hint about the underlying cause, and with reference chains, like the one in the example above, it can be a lot of work to figure out what actually throws it.
So a lot of code in Java applications tend to be code that walks down hierarchical data structures to retrieve leaf node values safely, avoiding throwing NPEs during of traversal.
There are two ways of handling values that are missing, when traversing the data structures
- Returning default values all the way down the data structure all the way to the leaf and continuing as if nothing bad happened
- Throwing a specific exception that can easily be traced in debuggers and stack traces, with as targeted a message text as possible, as soon as a hierarchy traversal fails
I used to write code confirming to the first strategy, but that gives failures that are hard to detect and hard to track down.
So I have switched to the second strategy: throwing a dedicated exception as soon as the traversal fails.
This means that a safe way to traverse a reference chain, like the one in the first example, would be something like this:
Integer findLeafnodeValue() { Something something = someService().findSomething(); if (something == null) { throw new MyAppException("Unable to find Something when looking for leafnodeValue"); } Other other = something.findOther(); if (other == null) { throw new MyAppException("Unable to find Other when looking for leafnodeValue"); } NestedOther nestedOther = other.findNestedOther(); if (nestedOther == null) { throw new MyAppException("Unable to find NestedOther when looking for leafnodeValue"); } return nestedOther.someValue(); }
If the API had been one returning Optional<> the code would have looked something like this:
Integer findLeafnodeValue() { Optional<Something> something = someService().findSomething(); if (something.isEmpty()) { throw new MyAppException("Unable to find Something when looking for leafnodeValue"); } Optional<Other> other = something.get().findOther(); if (other.isEmpty()) { throw new MyAppException("Unable to find Other when looking for leafnodeValue"); } Optional<NestedOther> nestedOther = other.get().findNestedOther(); if (nestedOther.isEmpty()) { throw new MyAppException("Unable to find NestedOther when looking for leafnodeValue"); } return nestedOther.get().someValue(); }
And this, I would have said, proved my point: no improvement, just extra clutter (wrapping the value I actually want in Optional<>, and having to call .get() to get the value I actually want).
My argument was that if Optional<>.get() could have been called safely without checking, then there may had been some point to it, at least following my old pattern of always returning sensible defaults.
But it’s not possible to call Optional<>.get() without first calling Optional<>.isPresent() (or Optional.isEmpty()) without having sonarqube complain about it (and not wise to do so… NoSuchElementException is just as useless as NullPointerException).
So again, my argument would have been: useless bloat.
But then I was introduced to Optional.orElseThrow(), and when using orElseThrow() the code becomes something like this:
Integer findLeafnodeValue() { Something something = someService().findSomething() .orElseThrow(() -> new MyAppException("Unable to find Something when looking for leafnodeValue")); Other other = something.findOther() .orElseThrow(() -> new MyAppException("Unable to find Other when looking for leafnodeValue")); NestedOther nestedOther = other.findNestedOther() .orElseThrow(() -> new MyAppException("Unable to find NestedOther when looking for leafnodeValue")); return nestedOther.someValue(); }
And this, even I have to admit, is shorter and clearer than the test-for-null version: no bulky if-tests with extra lines and curly braces.
And, alternatively using Optional.orElse(), it is possible to go back to the always-return-default, pattern.
Integer findLeafnodeValue() { Something something = someService().findSomething().orElse(new Something()); Other other = something.findOther().orElse(new Other()); NestedOther nestedOther = other.findNestedOther().orElse(new NestedOther()); return nestedOther.someValue(); }
This is similar to the way the JavaScript logical OR can be used to provide a default if a result is undefined. Somewhat more verbose, though.
It’s even possible to chain references, without risking a NullPointerException being thrown:
Integer leafnodeValue = someService .findSomething().orElse(new Something()) .findOther().orElse(new Other()) .findNestedOther().orElse(new NestedOther()) .someValue();
(Edit: see also Chaining Optionals using flatMap and map for a different way of chaining optional references)
So in conclusion: Optional can be useful.
Just stick to the following guidelines:
- Never have a field or local variable that is Optional<>, use fields and variabels of the wrapped type, instead
- Expose values that may be non-present using Optional<> in your own API
- Use .orElseThrow() or .orElse() on returned Optional<> values, to access the underlying variable, and assign to local variables and fields of the underlying type
- Never use .isPresent() or .get() (if you end up using them you’re probably doing something wrong)
Example code for this blog post can be found at https://github.com/steinarb/optionaldemo