Skip to main content
Java

How to Use Java Optionals to Handle Null Values Safely

5 mins

An umbrella protecting someone from a rainstorm, representing how Optionals shield your code from the common “storm” of NullPointerExceptions caused by null values

Null Values in Java #

Dealing with null values in Java can be one of the most common and frustrating sources of runtime errors, leading to the infamous NullPointerException. These errors often arise from failing to properly check for null values before accessing object methods or properties, resulting in unpredictable crashes.

Littering your code with null checks can make it harder to read and maintain

To avoid these issues, you need to check for null values before accessing them, which can lead to messy and error-prone code.

Java Optionals provide a cleaner and safer way to handle null values. Introduced in Java 8, Optionals are a container that may or may not contain a non-null value. They provide methods to check if a value is present, retrieve the value if it is present, and perform actions based on the presence or absence of a value.

Using orElse to Set a Default Value #

One common use case for Optionals is to provide a default value if the value is absent. The orElse method allows you to specify a default value that will be returned if the Optional is empty:

Optional<String> optional = Optional.empty();
String value = optional.orElse("default value");

You can also use ofNullable to wrap the return value of a method into an optional. Default values can then be provided using orElse.

String javaHome = Optional.ofNullable(System.getenv("JAVA_HOME")).orElse("NotSet");

Replace if-then-else Logic with Lambda Using ifPresentOrElse #

Consider the following example where we have a method searchDb() that returns a Person object based on an ID. If the person is found, the method returns an Optional containing the person object; otherwise, it returns an empty Optional.

private static Optional<Person> searchDb(String id) {
    // search the database and return the Person object result
    // ...
    return Optional.ofNullable(personResult);
}

Typically, you would check if the result is not null and then perform some action, otherwise perform another action.

This can be done using an if-then-else block:

Person result = searchDb("id2123");
if (result != null) {
    register(result);
} else {
    deregister("id2123");
}

If searchDb() returns an Optional, you can use the ifPresent method to perform an action if the value is present:

Optional<Person> result = searchDb("id2123");
result.ifPresent(p -> register(p));

note ifPresent returns a void, so it cannot be used to chain operations, such as orElse. Instead use ifPresentOrElse to chain operations. The following replaces the if-then-else pattern:

result.ifPresentOrElse(
        p -> register(p),
        () -> deregister("id123")
);

Simplify Exception Handling with orElseThrow #

Optionals can also simplify exception handling by providing a way to handle the absence of a value without using try-catch blocks.

You can use ifPresentOrElse to perform an action if the value is present, or throw an exception if the value is absent:

Optional<Person> result = searchDb("id2123");
result.ifPresentOrElse(
        p -> register(p),
        () -> { throw new RuntimeException("Person not found"); }
);

However, this only works for runtime exceptions. If you need to throw a checked exception, you will need to wrap it in a RuntimeException:

Optional<Person> result = searchDb("id2123");
result.ifPresentOrElse(
        p -> register(p),
        () -> { throw new RuntimeException(new PersonNotFoundException("Person not found")); }
);

orElseThrow is a more idiomatic way to handle null results

But a more idiomatic way to handle this is to use the orElseThrow method, which throws an exception if the value is absent, and will return of the value of the optional if it is present:

Person person = searchDb("id2123")
        .orElseThrow(() -> new PersonNotFoundException("Person not found"));

register(person);

Method Chaining Using Optionals for Cleaner Code #

Optionals provide a fluent API that allows you to chain multiple operations together. This can make your code more concise and easier to read. For example, you can chain map and filter operations to transform the value of an Optional:

Student student = new Student("John", 17);
Optional<Student> enrolled = Optional.ofNullable(student);

String username = enrolled.filter(s -> s.age() >= 18)
        .map(Student::name)
        .orElse("guest");

This non-optional code equivalent is:

if (student != null && student.age() >= 18) {
    return student.name();
} else {
    return "guest";
}

So, if there is a large number of conditions, the benefit of a chains of filter operations becomes more apparent.

Eliminate Nested Null Checks with the map Function #

Optionals can also help you avoid nested null checks by using the map method. The map method takes a function as an argument and applies it to the value inside the Optional if it is present. It then returns a new Optional containing the result of the function.

Here is the implementation of the map method in the Optional class:

public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    return !this.isPresent() ? empty() : ofNullable(mapper.apply(this.value));
}

Consider the following structure for an employee object:

record Employee(String name, Address address) {}
record Address(City city, String state) {}
record City(String name, String postcode) {}

If you want to get the postcode of an employee’s city, you would typically need to perform multiple null checks:

public String getPostcode(Employee employee) {
    if (employee != null) {
        Address address = employee.address();
        if (address != null) {
            City city = address.city();
            if (city != null) {
                String postcode = city.postcode();
                if (postcode != null) {
                    return postcode;
                }
            }
        }
    }

    return "Unknown";
}   

Using Optionals, you can simplify this code by chaining map operations:

public String getPostcode(Employee employee) {
    return Optional.ofNullable(employee)
            .map(Employee::address)
            .map(Address::city)
            .map(City::postcode)
            .orElse("Unknown");
}

This code is easier to read than the nested null checks, avoiding the need for multiple if statements.