Usage of the Java Optional Class

In Java
August 13, 2023

Overview

The Java Optional class is an elegant container for handling null values. Introduced in Java 8 and added to the Java.util package, the Optional class eliminates the need for numerous null value checks to protect your application from NullPointerExceptions at runtime.

Before the Optional class, many developers used null or exceptions to indicate the absence of a desired value. Now, by using the Optional class, you can easily check whether a value exists or not.

Why Should We Use Optional Class

Java, like most Object-Oriented Programming (OOP) languages, requires careful attention and control in type usage. For instance, let’s assume we have the following method:

public User getUser(); 

It’s clear that this method returns an object of type User, but it can also return another value: null. Since any non-primitive type can be set to null, there’s nothing preventing a developer from writing the following method implementation:

public User getUser() {
	return null;
}

This situation creates inconvenience for the client calling this method. Before using the User object returned by the getUser method, a client must check whether the object is null:

User user = getUser();

if (user == null) {
    // handle null case...
} else {
    // do something with the user object...
}

This approach ensures that the usage of the User object does not result in a NullPointerException (NPE). However, this creates another issue: any non-primitive object can implicitly be set to null. Therefore, we must extensively check for invalidity before using any returned object from a method. Not only is this not feasible in a large-scale application, but it also deteriorates the cleanliness of our code. For instance, adding extra lines for null checks introduces a boilerplate code problem across our application, making the usage of the User object less clear (hidden within an if-else statement).

The fundamental problem is not knowing when a method intends to return null. Because we are uncertain about this, we are forced to assume that any returned object could be null.

How To Use Optional?

By understanding the concepts behind the Optional class, we can now explore how Optional objects are practically utilized. The Optional class encompasses a wide range of methods that can be categorized into two groups: creation methods and initialization methods.

Creation Methods Optional creation methods are static methods that allow us to create various Optional objects to suit our needs. Currently, there are three methods of this type:

of

The ‘of’ static method permits us to wrap an existing object with an Optional. If the existing object is not null, a filled Optional is returned:

Optional<User> user = Optional.of(new User());  // Optional

ofNullable

The ‘ofNullable’ static method is similar to the ‘of’ method when a non-null value is passed (meaning a filled Optional is produced), but it will generate an empty Optional when null is passed.

Optional<User> user1 = Optional.ofNullable(new User());  // not-empty Optional
Optional<User> user2 = Optional.ofNullable(null);       // empty Optional

isPresent and isEmpty

There are two querying methods that allow us to check whether a specific Optional element is empty or not:

  • isPresent: Returns true if the Optional object is filled, and false if it’s empty.
  • isEmpty: Returns true if the Optional object is empty, and false if it’s filled.

Consequently, the querying methods of a filled Optional object will return the following:

Optional<User> books = // ...not-empty Optional...

books.isPresent();    // true
books.isEmpty();      // false

get

The ‘get’ method retrieves the value wrapped by the Optional object if the Optional is filled. If the Optional is empty, it throws a NoSuchElementException. This method is useful for converting an existing Optional into its value when we can guarantee that the Optional is filled. However, this method should be used with caution.

In practice, to ensure that an Optional is filled, we should first query the Optional using the isPresent or isEmpty methods and then call ‘get’:

Optional<User> userOptional = getUser();

if (userOptional.isPresent()) {
    User user = userOptional.get();
    // ...use the user object...
} else {
    // ...handle case of missing User...
}

orElseThrow

Similar to the OrElse method, the Optional class provides the orElseThrow method that allows us to throw an exception while attempting to retrieve the value of an Optional if it is empty. However, unlike the orElse methods, the orElseThrow method has two forms:

  • The form that doesn’t take any arguments: It creates a NoSuchElementException if the Optional is empty, or returns the wrapped value if the Optional is filled.
  • The form that takes a Supplier, which creates Throwable objects: It either throws the provided Throwable object if the Optional is empty or returns the wrapped value if the Optional is filled.

For instance, from an Optional object, we can obtain a User object as shown below:

Optional<Car> carOptional = doSomething();

Car car = carOptional
    		.orElseThrow();

If the Optional is empty, a NoSuchElementException is thrown. If the Optional is filled, it returns the wrapped value. Therefore, the orElseThrow method offers the same functionality as the get method, but its name better explains its purpose. Hence, the orElseThrow method should be used in places where an Optional needs to throw a NoSuchElementException when it’s empty, without first checking whether it’s filled (without using the isPresent or isEmpty querying methods).

ifPresent and ifPresentOrElse

The ifPresent method takes a Consumer that performs an action using the wrapped value if the Optional is filled. This serves as a functional alternative when we don’t want to perform any action if there is no value, unlike when using the orElse or orElseThrow methods to obtain the wrapped object.

Examples

Here are some examples to illustrate how Optional works:

Example 1: Creating Optional

import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        Optional<String> emptyOptional = Optional.empty();
        System.out.println(emptyOptional.isPresent()); // Output: false

        String value = "Hello, Optional!";
        Optional<String> optionalWithValue = Optional.of(value);
        System.out.println(optionalWithValue.isPresent()); // Output: true
    }
}

Example 2: Using orElse

import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        Optional<String> emptyOptional = Optional.empty();
        String result = emptyOptional.orElse("Default Value");
        System.out.println(result); // Output: Default Value

        String value = "Hello, Optional!";
        Optional<String> optionalWithValue = Optional.of(value);
        String result2 = optionalWithValue.orElse("Default Value");
        System.out.println(result2); // Output: Hello, Optional!
    }
}

Example 3: Using isPresent and ifPresent

import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        Optional<String> emptyOptional = Optional.empty();
        if (emptyOptional.isPresent()) {
            System.out.println(emptyOptional.get()); // Will not be executed
        }

        String value = "Hello, Optional!";
        Optional<String> optionalWithValue = Optional.of(value);
        optionalWithValue.ifPresent(val -> System.out.println("Value: " + val)); // Output: Value: Hello, Optional!
    }
}

Example 4: Using orElseThrow

import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        Optional<String> emptyOptional = Optional.empty();
        try {
            String result = emptyOptional.orElseThrow(() -> new Exception("Value not present"));
        } catch (Exception e) {
            System.out.println(e.getMessage()); // Output: Value not present
        }

        String value = "Hello, Optional!";
        Optional<String> optionalWithValue = Optional.of(value);
        String result = optionalWithValue.orElseThrow(() -> new Exception("Value not present"));
        System.out.println(result); // Output: Hello, Optional!
    }
}