Optional Shouldn’t Be Optional

The Optional Horror Show

I’ve been reading-up on Java 8’s Optional<T> class and it’s proper usage. It might not surprise you that opinions on its correct usage vary greatly and passionately.

  • Only for return values never arguments
  • Use it everywhere!
  • Never as a field value
  • Only for return values…but only sometimes
  • Not for all getters…but some
  • Let’s make it serializable!

Even the experts don’t have consensus. So why was optional introduced? As I understand it, its primary purpose was to make the stream API more fluent and to return a reasonable result from terminal stream operations. Terminal operations need to return a result even when a collection is empty. For example:

x = s.findFirst().or(valueIfEmpty)

Nicolai Parlog wrote an excellent article discussing the background of Optional. I suggest everyone read it. It gives a great summary of the back and forth that went on around the introduction of Optional<T>.

I will use Optional<T> but ultimately I think it was a misstep. My primary issue with Optional is that it is a wrapper class that must be instantiated. Because of this I have to decide when to use it and when not to use it. This will lead to many different styles of using Optional and to an overall proliferation of use. For example:

  • Do we use Set<Optional<T>> and Map<K, Optional<V>>? Some people will and some people wont.
  • Do we have Java Bean getters return Optional<T>? If the answer is “sometimes” then how is that not a problem?
  • Because Optional<T> is a wrapper class it cannot simply implement Serializable – if it did it would have indeterminate behavior when it wraps a non-serializable class.
  • Most of the existing Java SDK classes do not and will not use Optional<T>. So I will have to accommodate to styles anyway.

With Optional as a wrapper class so many frameworks have been impacted and thus need to change. For example:

  • JSON, XML, JPA, etc. libraries need to consider Optional in order to properly read and write values from getters that return an Optional<T>.
  • Bean Validation needs to unwrap Optional<T> and validate its value…but wait, what if we want @NotNull bean constraint on the Optional itself and not it’s wrapped value?
  • Existing Java SDK classes send an unclear message on optional. They do not use Optional<T> so now we have to take care ANYWAY when using return values.

The decision to introduce Optional as a wrapper class has a far-reaching impact and it’s usage will proliferate and, worse yet, proliferate in an inconsistent way. At this point I do not think the Genie can be put back into the lamp.

Do Alternatives Exist?

If you think about it, Optional is really just behavior it is not state. For example, Optional allows us to write fluent code like this:

String value =
   Optional.ofNullable(map.get("foo"))
      .orElse("bar");

Optional behavior does require any properties outside of the object it is operating on. Now, I am not a language designer. I assume everyone that worked on defining Lambda is much smarter than I am. Nonetheless, I cannot wrap my head around the decision to introduce the Optional<T> wrapper. Wouldn’t life have been easier (even if done as a hack) to allow all objects (including null) to accept methods like #ifPresent, #orElse, and #map? Wouldn’t making this behavior available to all variables solve many of the issues associated with making Optional<T> a wrapper class?

  • There would be no Optional class to ponder how best to use.
  • Stream terminal operations work fine.
  • There are no issues regarding whether it should be a field or not, serializable or not, etc.
  • Creation semantics between #ofNullable and #of
  • Existing frameworks that deal with beans wouldn’t need to change.
  • No need to accommodate older classes that do not return an Optional<T>.

If we go back to our earlier example, instead of this:

String value =
   Optional.ofNullable(map.get("foo"))
      .orElse("bar");

We would be able to always and safely write this:

String value = map.get("foo").orElse("bar");

No code needs to re-written to make use of optional behavior, you no longer have to complain that an Optional should be serializable, wonder where you should use a normal object or instantiate an Optional wrapper for that object, etc. And you would never wonder if you should declare a map as Map<String, Optional<String>>.

So you may wonder why the Java experts didn’t make null and java.lang.Object respond to the Optional<T> methods. Well this idea does have at least one VERY BIG issue. If your code (or any code you depended on) happened to contain a class with a method named #ifPresent, #orElse, or #map you’d be screwed – especially if any of those methods had an incompatible signature. Your code would not compile. Wow…that sucks, built-in Optional methods seemed like such a simple solution.

Is there a way around this issue? Well, some languages use an operator instead of methods for optional behavior and that could get us past part of the problem. It’s sometimes called an Elvis Operator or Null Coalescing Operator. For example, in Groovy or PHP you might write the above code like this:

String value = map.get("foo") ?: "bar";

You can think of it as shorthand for:

String value = map.get("foo") != null ? map.get(“foo”) : "bar";

That works great for #orElse, but it doesn’t give us that cool #ifPresent functionality. For example:

json.getUserName().ifPresent(dbRecord::setUserName);

With an Elvis operator you’d have to do something like this:

dbRecord.setUserName(json.getUserName() ?: dbRecord.getUserName());

As you can see the two variants are not functionally the same. #ifPresent allows us to complete forgo calling dbRecord#setUserName when json#getUserName is not present. So I suppose we’d need something more than a null coalescing operator to get rid of the Optional<T> class but perhaps for Java 8 that one operator would have been enough.

The JDK 8 team had many discussions about this, so clearly there is no perfect solution. Here are some ideas I’m sure they threw around:

  1. Make all objects (including null) respond to #ifPresent, #orElse, and #map and let the chips fall where they may. If your code or a library has a class that uses these methods then you get a compilation error. You either can’t use the latest version of Java or you have to update your code and/or libraries.
  2. Make a special message send operator for these built-in and universal methods (system messages). For example, value:ifPresent or value->ifPresent. Ew. I don’t like this idea but I still like it better than I like instantiating Optional wrapper objects.
  3. Create an Elvis operator and leave out friends like #isPresent and #map. Again, not my favorite choice but still better than instantiating Optional<T>

My preference is #1. But hey, I’m not responsible for the adoption of an entire language upgrade. I wouldn’t get the heat for any broken code. Then again, most popular libraries respond quickly to changes. If I think about it, the slight chance you may have to remove an existing #ifPresent, #orElse, or #map from a library is probably less effort than deciding what to do if a Java Bean has a getter that returns an instance of Optional<T>. And code that is mine can usually be fixed quickly. Most issues could be determined at compile time. I don’t think there are a TON of classes that have these methods out there. I did a search on GitHUB and most of the hits for these method names use the same method signature as Optional – so those might be able to stay. Is it a reckless choice? Yup. But look at the alternative. Every day more and more libraries are having to adopt and adapt to use Optional<T>s (Jackson, Gson, Jersey, Hibernate, Immutables, Lombok, Spring, etc.) and everyday users are introducing more and more fucked-up usages of Optional<T>.

So while there are no perfect solutions there are better solutions than a wrapper class with ambiguous semantics. The presence of Optional<T> behavior on an object should not be optional. Optional behavior should be implicit on every object whether it is null or not. Introducing Optional<T> into Java 8 was a simple solution to a complex problem. But it has introduced its own set problem that are far reaching and will last in perpetuity.

So how will I use Optional<T> now? Until something else comes along I will follow the advice of Brian Goetz.