I have to confess, I was sceptical when I first read about the new Optional class that was being introduced in Java 8 way back when. Its only over the last few years I have come to really appreciate how it can shape the way we define method signatures and invoke them. This post covers the various ways this simple class can help you write clearer and more robust code.

When returning a value from a method, you want to return one of three things, either return the actual result, return an unknown (null) value, or throw an exception.
The first case is the happy path which most developers code for as it hopefully meets most cases. Returning a null can often be overlooked unless the developer is very conscientious or there is already an expectation that the result is likely to be null. The third option is that an exception can be thrown from the method if it is unable to complete the operation. The problem here is that we have a fairly loose contract with the client and leave it up to the caller to be aware of the different outcomes.

Once we have a value from a method, you typically want to do one of three things when it is null. Use a default value, throw an exception or change program flow to not use it. The following 3 examples show how this is done in a pre-Optional world:

//Throw Exception
Integer size = calculateSize();
if (size == null) {
  throw new NullPointerException("Couldn't calculate size");
}
//do something with size.


 
//Default Value
Integer size = calculateSize();
if (size == null) {
  size = DEFAULT_SIZE;
}
allocateSpace(size);


//Different code path:
Integer size = calculateSize();
if (size != null) {
  allocateSpace(size);
}

//carry on

Because we have always been able to deal with null values like this, I wasn’t blown away by the prospect of the Optional class. However, the problem with the above is that we must always remember to handle a null result and inevitably at some point, someone will end up writing :

Integer size = calculateSize();
allocateSpace(size);

This will create code that is prone to sporadic unhandled failures. In general we want our code to be robust, easy to use and offer the least surprises.

To avoid repeating this in every invocation, we could roll the logic into our function so calculateSize() will throw an exception or return a default value. However, we might not always know which we want to do since it would depend on the context it was called in.

Optionals To The Rescue

Optional values allow us to always return an object that acts as a container for the actual value that may or may not be null. We typically create a new Optional using either :

return Optional.of(result);

or

return Optional.ofNullable(possibleNullValue);

If the value you return may be null, you should use the latter version otherwise an exception will be thrown if you invoke the first syntax with a null value.

Optional values enable us to define the signature of the method to say the outcome could well be null and force the client to deal with it.

We can query the returned Optional value and handle it differently depending on whether there is a value or not. Here are the same above 3 cases implemented as an Optional value returned from calculateSize():

//Default Value
Integer size = calculateSize().orElse(DEFAULT_SIZE);
allocateSpace(size);

//Throw Exception
Integer size = calculateSize().orElseThrow(()->new RuntimeException("Couldn't calculate size"));
allocateSpace(size);

//Different code path
calculateSize().ifPresent(s-> allocateSpace(s));

The real value of the Optional class is the semantics to say that this value could be null, forcing the developer to deal with it and providing convenience for handling null values in a different way. Granted, just as before, someone could just incorrectly write :

Integer size = calculateSize().get();
allocateSpace(size);

But there are static analysis tools that can detect such code smells where you access an optional without checking it, and if you have proper unit test coverage (such that calculateSize returns an empty Optional) this code should be invoked with an empty optional and an error thrown.

Improving clarity.

When we return the actual object, callers of the method don’t know whether null could be returned or if the method will throw an exception without looking at either the method signature or java doc. When returning an Optional, you can safely assume that the value can be null and code defensively for it either by using one of the above solutions.

Rules to code by:

  • If a value will always be returned then return the actual type.
  • If the method could return null, then return an Optional to let the caller know this is the case and force the client to deal with the result.
  • Throw an exception, select default or change the program flow based on the result for the appropriate context of the caller.