DEV Community

David Schmitz
David Schmitz

Posted on

Save the Optional, stop using isPresent

Most functional programming languages offer a concept called Option or Maybe to deal with the presence or absence of a value, thus avoiding null. Java 8 introduced java.util.Optional, an implementation of the Maybe type for Java developers. Sadly, due to its flexibility, Optional is often misused, be it because the developer does not understand its power, or be due to lack of background in functional programming.

In this post I want to highlight a common pattern of misusing Optional and how to fix it.

Note that instead of java.util.Optional, I will use the Vavr Option instead. Vavr is a lightweight library that brings Scala-like features to Java 8 projects. It focuses on providing a great developer experience both through consistent APIs and extensive documentation. See this short overview of how and why optional can help you. Head over to http://vavr.io if you want to know more.

But, everything here applies to either implementation.

A real world example

I want to start with a typical example that we can use as a refactoring candidate.

Let's consider the following use case: Load a user using a repository. If we find a user we check if the address is set, and if so, we return the street of the address, otherwise the empty string.

Using nulls we write code similar to this:

User user = repo.findOne("id");
if (user != null) {
  Address address = user.getAddress();
  if (null != address) {
    return address.getStreet();
  }
  else {
    return "";
  }
}
Enter fullscreen mode Exit fullscreen mode

Urgs. This is what I call a Cascading Pile of Shame.

Fixing this is easy, just use Option:

Option<User> opt = Option.of(user);

if (opt.isPresent()) {
  Option<Address> address = Option.of(user.getAddress());
  if (address.isPresent()) {
    return address.get().getStreet();
  }
  else {
    return "";
  }
}
Enter fullscreen mode Exit fullscreen mode

Right?

Wrong! Each time Option is used like this, a microservice dies in production. This fix is basically the same as above. Same complexity, same _Cascading Pile of Shame.

Instead we use the map operator.

Map - the Swiss army knife of functional programming

map is your friend when using Option. Think of Option as a nice gift box with something in it.

Suppose you are a good programmer and wrote your code Test-First. You get a gift box with socks.

Gift box

But who wants socks? You want a ball. So you map a function to the gift box, that takes socks and transforms them to a ball. The result is then put into a new gift box. Your birthday is saved through the power of monads.

Mapping to a ball

What if you are a bad coder and do not write unit tests at all? Well, then you won't get any nice socks. But map still works fine:

Nothing from nothing

If the gift box is empty, then map won't even apply the function. So, basically it is "nothing from nothing".

Fixing things

So going back to the original problem, let's refactor this using Option.

User user = repo.findOne("id");
if (user != null) {
  Address address = user.getAddress();
  if (null != address) {
    return address.getStreet();
  }
}
Enter fullscreen mode Exit fullscreen mode

First of all, let findOne return Option<User> instead of null:

Option<User> user = repo.findOne("id");
...
Enter fullscreen mode Exit fullscreen mode

Since the user's address is optional (see what I did there ;) User#getAddress should return Option<Address>. This leads to the following code:

Option<User> user = repo.findOne("id");
user.flatMap(User::getAddress)
...
Enter fullscreen mode Exit fullscreen mode

Why flatMap...well, I'll leave that as an exercise. Just remember, that User#getAddress return Option<Address> and think about, what would happen if you used map?

Now that we've got the Option<Address> we can map again:

Option<User> user = repo.findOne("id");
user.flatMap(User::getAddress)
    .map(Address::getStreet)
...
Enter fullscreen mode Exit fullscreen mode

Finally, we only need to decide what to do if everything else fails:

Option<User> user = repo.findOne("id");
user.flatMap(User::getAddress)
    .map(Address::getStreet)
    .getOrElse("");
Enter fullscreen mode Exit fullscreen mode

Which leaves us with the final version:

repo.findOne("id")
    .flatMap(User::getAddress)
    .map(Address::getStreet)
    .getOrElse("");
Enter fullscreen mode Exit fullscreen mode

If you read it from top to bottom, this is as literal as it gets.

repo.findOne("id")                // Find a user
    .flatMap(User::getAddress)    //   if an address is available
    .map(Address::getStreet)      //   fetch the addresses street
    .getOrElse("");               //   otherwise use the empty string
Enter fullscreen mode Exit fullscreen mode

Summary

I hope this short post illustrates the usefulness of Vavr and its Option abstraction. If you remember one thing only, then please let it be Do not use Option#isPresent or Option#get, the map is your friend.

Vavr as a library offers many amazing extensions for object-functional programming in Java, even for brownfield projects. You can leverage its utilities where they make sense and need not migrate to Scala or similar platforms to reap at least some benefits of functional programming.

Of course, this is all syntactic sugar. But as any good library, Vavr fixes things, the core JDK cannot take care of so easily without breaking a lot of code.

Future posts will cover its other amazing features like pattern matching, property based testing, collections and other functional enhancements.

Latest comments (3)

Collapse
 
phouchens profile image
Perry H

"Each time Option is used like this, a microservice dies in production."
😂😂
Nice article!

Collapse
 
alebianco profile image
Alessandro Bianco

I find the 'exercise for the user' quite confusing since no clear signature for those methods are given and the return type changes between the examples.

Anyway, if anyone is wondering, flatMap is used because getAddress returns an Option<Address> in one of the examples. map is used instead with 'getStreet' because it returns a plain String.

Collapse
 
koenighotze profile image
David Schmitz

Good point, thank you. I have added a small extra sentence, that hopefully reduced the confusion.

To add to your answer: flatMap expects a mapper that returns an Option. map on the other hand wraps the result of the applied mapper function into either some or none.

In short: map creates a new "Gift Box" and flatMap reuses the "Gift Box".