Optional

What it is and why use itJuly 4, 2020

It's been a while since I did my last dev article. Lots of stuff happening these days that "stress management" is now part of my vocabulary. What's fun though is that this article was supposed to be published last year. And draft notes suggest that it was last worked on a year after I stopped writing dev articles. This could be one of the first drafts that I left unpublished!

Anyways, I'm writing about Option, using Java's Optional for the examples. Why talk about Option? Because recent events reminded me of a conversation I had with other developers about Optional and how they misunderstood its purpose. When they see it as a fancy wrapper around a null-check, it's a cause for concern.

The problem

When we write code, we often do it with only the "happy path" in mind. In the following example, we use getPersonWithId(), pass it an ID, assume we get a Person instance, and attempt to call getAge() on it to get an age value.

public class MyPeopleService {

    Person getPersonWithId(int id) {
        // return instance of Person when found
        // return null when not found
    }

    void doSomethingWithPersonWithId(int id) {
        int age = this.getPersonWithId(id).getAge()
    }

}

But when getPersonWithId() returns a null, all hell breaks lose! Code gets refactored, developers debate on best practices, people leave, deadlines get pushed, project managers panic, and everyone starts pointing fingers at each other! Or at least that's the scenario that runs in everyone's heads while staying calm and professional. But still! Chaos!

Anyways, this class of error is commonly called a Null Pointer Exception (NPE). It's when our application is written to expect a value, but the value returned was null. When code receives a null and starts using it like an object, it causes the application to throw an exception and possibly crash the application.

But it's not entirely the developer's fault though. There are several other problems in this snippet:

  1. null is a valid value for the Person type.
  2. The compiler does not force the developer to deal with a potential null.

So how do we deal with such problems then?

Option to the rescue

Option is a type that represents the presence or absence of a value. The concept is similar to JavaScript's Promise, which is an object that represents the eventual result of an asynchronous operation. It's a wrapper object essentially.

Below is the same operation above. But instead of getPersonWithId() returning a value of type Person, we return an Optional wrapping a value of type Person.

public class MyPeopleService {

    Optional<Person> getPersonWithId(int id) {
        // return instance of Person when found
        // return Option when not found
    }

    void doSomethingWithPersonWithId(int id) {
        // Get age of potentially present person
        // default to 0 if absent
        int age1 = this.getPersonWithId(id)
            .map(p -> p.getAge())
            .orElse(0);

        // Get age of potentially present person
        // throw error if absent
        int age2 = this.getPersonWithId(id)
            .map(p -> p.getAge())
            .orElseThrow(PersonNotFoundException::new);
    }

}

Optional solves the first problem by having APIs return one type, an Optional instead of two kinds values, the value which is of type Persson or null. This way, it eliminates the use of null, allows the caller to expect only one type of value, and prevent the breakage of code between caller and callee.

Optional solves the second problem this way: To safely extract the value present in an Optional, it forces us to define the behavior in the event that it is empty. That's right, an Optional does not give away the wrapped value without either defining a default value or throwing an explicitly-defined exception.

That said, Optional differs from a regular null-check in that it's a type, it can be type-checked at compile time. It forces us to always handle the empty case, unlike a null check which is often added as an afterthought. Also, it makes you really think of what should happen to the application when it encounters a missing value.

Conclusion

Optional is somewhat like the lottery. We join the lottery by buying potentially winning or losing tickets. At the same time, we've also implicitly signed the lottery's terms and conditions regarding winning and losing. With all that set, we can now win or lose the lottery without any ambiguity between us and the parties involved.