Do we have a better Option here?

Michał Chmielarz

14 Jun 2017.7 minutes read

What’s the deal?

Have you ever fought with null values or with (in)famous NullPointerExceptions 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 nulls 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 Optionals:

// 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:

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!

Blog Comments powered by Disqus.