Do we have a better Option here?
Have you ever fought with null
values or with (in)famous NullPointerException
s in your project? I think almost all of us struggled with them at one point or another, looking for their origin and the underlying cause. Does a given null
value stand for a valid value? Or is it a result of messy code with no proper failure handling and signals some error?
In situations like this, returning an optional value from a method or a function does the trick. We can return some/something for a valid value and none/nothing to indicate its absence. Handling of error cases can be done with other monadic containers (like Scala’s Try
or Either
) but this is a topic for yet another blog post.
By employing an optional type we reduce the presence of null
s in the code. I think this is the most important gain. What is more, we have the possibility to show our intentions in the code in much cleaner way than previously. Other human beings, reading our code, would not have to ponder what a null
means in a given place. After all, we all spend more time reading some code than writing it, right?
Keep in mind that using optionals requires some coding convention established in a project. Functions returning
null
, while declared to return an optional value, can cause a lot of harm.
In the Java world we have a bunch of implementations of such type that we could use in a project. In this post I would like to compare two of them: java.util.Optional
from the standard library and io.vavr.control.Option
from Vavr library.
Similarities and differences
Differences show up pretty soon. The standard library offers just a single Optional
class to handle existence of a value and its lack. On the other hand, Option
coming from Vavr library is an interface having two implementations, i.e. Some
and None
, supporting both cases, respectively.
Their instantiation is pretty straightforward. Optional
instance can be created with factory methods from the class. And while creation of such object is rather a dull task, there is one catch you have to be aware of. When calling Optional.of(val)
method, you have to be sure that val
is always a non-null value. Otherwise, the operation throws NullPointerException
- ironically, in the moment when you try to avoid such a nasty thing.
Of course, you can use Optional.ofNullable(val)
in such case. However, why could this not be just a single method, resulting with a slightly simpler API?
Moreover, such behaviour of these two methods can results in more serious problems as it is shown in The agonizing death of an astronaut (and in links provided there at the end of the article).
Vavr offers two ways to create an instance of Option
type - through factory methods from the interface or with their aliases from io.vavr.API
class. During creation of an instance you are protected from NPEs in all cases. Calling Option.of
returns None
instance for null
values provided.
// creating Optional:
Optional.of(value); // Optional[value]
Optional.of(null); // is going to fail with NPE
Optional.ofNullable // Optional.empty
// creating Option:
Option.of(null) // None, will not explode with NPE
Option.of(value) // Some[value]
Option.some(null) // Some[null], there is no way to do this for Optional
Option.none() // None
What about consuming values of both types? With Optional
you have two ways: use ifPresent(func)
or check presence of a value with isPresent()
and then get the value to do something with it. Here is how this can be done:
// we can consume Optional like this:
optionalValue.ifPresent(v -> log.info(“Value: {}”, v))
if (!optionalValue.isPresent()) {
log.info(“No value”);
}
// or this way:
if (optionalValue.isPresent()) {
log.info(“Value: {}”, optionalValue.get());
} else {
log.info(“No value”);
}
We can do a similar thing using Option
type as well. The first case can be implemented with forEach(action)
method and the latter can be handled in almost the same way by using isDefined/isEmpty
methods.
// consuming Option no. 1:
optionValue.forEach(v -> log.info(“Value: {}”, v))
if (optionValue.isEmpty()) {
log.info(“No value”);
}
// consuming Option no. 2:
if (optionValue.isDefined()) {
log.info(“Value: {}”, optionValue.get());
} else {
log.info(“No value”);
}
However, I think there is an even better way to consume an optional value with Option
type. By the use of peek(func)
instead of forEach
method we can chain onEmpty(func)
method call that handles a presence of no value. This way, we do not have to check for presence of a value with if
statement and our code becomes cleaner:
// consuming Option, the better way:
optionValue
.peek(v -> log.info(“Value: {}”, v))
.onEmpty(() -> log.info(“No value”));
There is yet another difference between Option
and Optional
types when it comes to working with an alternative value of an empty option. Standard library offers orElse
methods returning a plain value, not wrapped with the Optional
container. They are useful and do the work:
// assumption - findBy(postalCode) returns an Optional instance
// alternative value for an empty Optional:
Address addr = findBy(postalCode).orElse(defaultAddress);
// or
Address addr = findBy(postalCode).orElseGet(() -> findBy(streetAndCity));
But what if you would like to have the alternative value returned as Optional
? Here is the code that does the thing, however it is far from perfect:
// using flatMap for an alternative wrapped with Optional:
Optional<Address> addr = findBy(postalCode)
.flatMap(addr -> addr == null ? findBy(streetAndCity) : addr);
On the other hand, if you take a look on the Option
from Vavr, you notice variants of orElse
methods returning an alternative but still wrapped with the container. Such solution gives a possibility to end up with a more compact code:
// Option's alternative value:
Option<Address> addr = findBy(postalCode)
.orElse(() -> findBy(streetAndCity));
Overall, Vavr offers almost countless possibilities of handling an option value. This is a result of implementing io.vavr.Value
interface. It provides a lot of type conversion and iterable methods. We can, for example, transform a value into a collection (from JDK or Vavr) with a single element (or empty in case of None
).
Moreover, working with a collection of option values is currently easier with Option
type than with `Optional.
// transforming optional postal codes into addresses
// with JDK’s Optional:
postalCodes.stream()
.map(o -> findAddressBy(o))
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
.collect(toList());
// or
postalCodes.stream()
.map(o -> findAddressBy(o))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toList());
// with Vavr’s Option:
postalCodes.
.map(o -> findAddressBy(o))
.flatMap(Option::toStream);
Unfortunately, Optional
type has no such capabilities since it works just as a wrapper for some value.
JDK9 and Optional
What about Optional
updates coming with JDK9? As you can see in the documentation, there are three new methods proposed.
The first one is void ifPresentOrElse(func, func)
, which is an extended version of ifPresent(func)
method. It solves the necessity of using an if-statement when reacting to an empty value. While I prefer the solution provided in Vavr with Option.onEmpty
, the method does the work. Here is how you could use it:
// consuming value with ifPresentOrElse:
optionValue.ifPresentOrElse(
val -> log.info(“Value: {}”, val),
() -> log.info(“No value”)
);
Next, the second method is a conversion of an optional value into a stream with Stream<T> stream()
method. This one simplifies processing collection of Optional
s:
// earlier example of fetching addresses by postal codes can be written like this:
postalCodes.stream()
.map(o -> findBy(o))
.flatMap(Optional::stream)
.collect(toList());
And the third one is Optional<T> or(supplier)
method, that allows to provide a default value for an empty one but wrapped with Optional
.
// alternative value with JDK9’s Optional:
Optional<Address> addr = findBy(postalCode)
.or(() -> findBy(streetAndCity));
Vavr’s updates
Recently, the authors of the Vavr library have no plans to change or extend the API of the Option
type. They’re aiming to keep it stable.
However, there are two issues open in the project’s GitHub, related to the Option
type:
- “sealing” of core interfaces and
- implementation of “applicative lifting” of algebraic types.
Sealed interfaces should be available from the release of 1.0.0 version and the lifting feature is still in the phase of discussing its usability and design.
Conclusion
As you read above, the Option
is a way more sophisticated type than the standard Optional
. Its API is really rich and powerful, resulting with a great flexibility. Additionally, the type comes from a library that is lightweight in terms of dependencies - Vavr has no transitive dependencies at all.
Moreover, Vavr has nice integrations with other libraries like cyclops, resilience4j and Jackson. It seems that with the next release, Spring Data will have the support of the Option
type as well (it is in transition from Javaslang to Vavr). However, while the support of external tools looks good, the fact that Vavr is a non-standard library means you will have to sometimes provide a custom integration layer with other libraries.
What about the Optional
type then? It has been designed as a simple wrapper for an optional return value. Its API is really simple and using it usually ends up with a couple of if-statements in a code. Moreover, it is neither serializable nor implements the java.util.Iterable
interface. However, it comes from a standard library which translates to a broad usage and support.
Which one to choose? If you just need a wrapper, then the standard Optional
looks just right for the use case. However, having possibility to add Vavr library to a project, I would definitely opt for Vavr’s Option
because of all the gains mentioned above.
Have some thoughts about optionals in Java? Share them with us in comments!