Optionals in Java

Optionals in Java

A concise explanation on Java Optionals

Introduction

Optional is a class introduced in Java 8 to handle null values in order to avoid the dreadful NullPointerException (NPE). It is a wrapper class that can be used to represent a value that may be null or not.

Before Java 8, developers had to use null checks to handle null values.

Let's say we have a method that returns an employee from a database.

public static void main(String[] args) {
    var employee = getEmployee("John");
}
public Employee getEmployee(String name) {
    Employee employee = getEmployeeByName(name);

    if (employee != null) {
        return employee;
    } else {
        return throw new IllegalArgumentException("Employee not found");
    }
}

This is the equivalent code with Optional.

public static void main(String[] args) {
    var employee = getEmployee("John");
}
public Optional<Employee> getEmployee(String name) {
    Optional<Employee> employee = getEmployeeByName(name);

    if (employee.isPresent()) {
        return employee.get();
    } else {
        return employee.orElseThrow(() -> new IllegalArgumentException("Employee not found"));
        // return Optional.of(new Employee());    // or return a default employee
        // return Optional.empty();               // or return an empty optional
    }
}

But, wait! what is the point of the Optional class if we can achieve the same result using a null check?

There is a subtle difference: when a method returns an Optional, that method is indicating that it may or may not return a value. So you have to be prepared to handle both cases. But that is not a big improvement over the null check.

Real improvements with Optional

The Optional API provides additional methods.

public static void main(String[] args) {
    var employee = getEmployeee("John");
}

The orElse() method returns the wrapped value or the provided value as if the optional is empty. It's preferred over the get() and isPresent() methods.

public Optional<Employee> getEmployeee(String name) {
    return getEmployeeByName(name)
               .orElse(new Employee());
}

Chaining stream operations

Let's try to retrieve an employee age from the database using null checks.

public int getEmployeeAge(String name) {
    Employee employee = getEmployeeByName(name);

    if (employee != null) {
        if (employee.getAge() != null) {
            return employee.getAge();
        } else {
            return throw new IllegalArgumentException("Employee age is null");
        }
    }
}

It's a bit ugly and cumbersome. More important, forgetting one null check can cause a NPE. The code above can be simplified by chaining stream operations.

The Optional API provides a much more robust code.

public Optional<Integer> getEmployeeAge(String name) {
    Optional<Employee> optionalEmployee = getEmployeeByName(name);
    return getEmployeeByName(name)
               .map(Employee::getAge)
               .orElse(0);
}

We might want to throw an exception if the age is null.

public Optional<Integer> getEmployeeAge(String name) {
    return getEmployeeByName(name)
               .map(Employee::getAge)
               .orElseThrow(() -> new IllegalArgumentException("Employee age is null"));
}

Do not overuse Optional

Optional can also be used for method parameters, but it does not make any sense. It shoul be used only for method return values.

// Strange use of Optional
public Optional<Integer> getEmployeeAge(Optional<String> name) {
    ..................
}

Bonus: how other languages handle null values

Scala

Scala, one important JVM language, uses the Option class, which is a wrapper class.

def getEmployeeAge(name: String): Option[Int] = {
  val employee = getEmployeeByName(name)
  employee.map(_.age)
}

Kotlin

Other languages use an operator instead of a wrapper object to handle null values. In Kotlin the ? is called the safe call operator.

// In Kotlin the returned data type is put after the semicolon
fun getEmployeeAge(String name): Int? {
    val employee = getEmployeeByName(name)
    return employee?.age // similar to `orElse(null)` in Java
}

If you want to provide a default value, you can use the ?: operator.

val age = getEmployeeAge("John") ?: 0  // similar to `orElse(0)` in Java

Outside the JVM world, C# also uses the ? or null-conditional operator to indicate that a variable may be null.

C#

public int? GetEmployeeAge(string name) {
    var employee = GetEmployeeByName(name);
    return employee?.Age;
}

Conclusion

As we saw, Optional is not just a replacement for null checks. It is a wrapper class that can be used to represent a value that may be null or not. We can appreciate its real power when we chain stream operations. The Optional methods get() and isPresent() are discouraged to use. So much that the language designers regret having creating them.

Other languages like Kotlin and C# use the ? operator to handle null values. Scala provides a similar solution.