byandrev's blog

6 min read

Optional in Java: how to say goodbye (almost) to NullPointerException

Since Java 8, the language includes a tool specifically designed to solve this problem: the Optional class. In this article, we will look at what it is, why it exists, and how to use it correctly with code examples.

The Problem It Solves

Imagine this classic method:

java
public User findUserById(String id) {
    // may return a User or null if not found
    return db.get(id);
}

And then, somewhere else in the code:

java
User user = findUserById("123");
System.out.println(user.getName()); // NullPointerException if it doesn't exist

The real problem isn't just that the program crashes. It's that the method doesn't warn you. It is an invisible contract, as the rule exists because there might not be a user, but it isn't written anywhere visible. It is only discovered when the NullPointerException occurs.

NullPointerException meme
NullPointerException meme

What is Optional?

Optional<T> is a container that can either hold a value of type T or be empty. Internally, it is basically a box with a reference that can be null or not, but it forces you to handle it explicitly instead of letting you access the value "blindly".

Let's rewrite the previous example:

java
public Optional<User> findUserById(String id) {
    User user = db.get(id);
    return Optional.ofNullable(user);
}

Now, anyone calling this method knows that the user might not exist.

Creating an Optional

There are three main ways to create one:

java
// 1. When you are SURE the value is not null
Optional<String> name = Optional.of("Ana");
 
// 2. When the value CAN be null
Optional<String> potentiallyNullName = Optional.ofNullable(potentiallyNullName);
 
// 3. An explicitly empty Optional
Optional<String> empty = Optional.empty();

Be careful with Optional.of(); if you pass null, it throws an exception immediately (NullPointerException). It is only used when you are certain that the value exists. For everything else, ofNullable is your friend.

Checking if a Value is Present

java
Optional<User> result = findUserById("123");
 
if (result.isPresent()) {
    System.out.println("Found: " + result.get());
} else {
    System.out.println("This user does not exist");
}

This works, but it is practically the same pattern as an if (user != null) check. We are not leveraging what Optional truly offers.

The Best Way: Avoid get()

Calling .get() without checking if a value is present first is a very common anti-pattern. If the Optional is empty, .get() throws a NoSuchElementException, so you would only be swapping one type of exception for another.

Instead, Optional offers much more expressive methods:

orElse: a default value

java
String name = findUserById("123")
    .map(User::getName)
    .orElse("Unknown user");

orElseGet: a default value

java
String name = findUserById("123")
    .map(User::getName)
    .orElseGet(() -> getDefaultNameFromConfiguration());

The difference with orElse is subtle but important: orElseGet only executes the Supplier if it is actually needed. orElse always evaluates its argument, even if a value is present. If calculating the default value is expensive (a database call, for example), always use orElseGet.

orElseThrow: throwing a meaningful exception

java
User user = findUserById("123")
    .orElseThrow(() -> new UserNotFoundException("User 123 does not exist"));

This is much clearer than a generic NullPointerException since the exception explains exactly what happened.

Transforming Values with map and flatMap

This is where Optional becomes very useful, as it allows you to chain operations without having to nest if statements everywhere.

java
Optional<User> user = findUserById("123");
 
Optional<String> nameInUppercase = user
    .map(User::getName)
    .map(String::toUpperCase);
 
System.out.println(nameInUppercase.orElse("NO NAME"));

If user is empty, each map is simply skipped and the final result is an empty Optional. There is no need to check for nulls at each step.

flatMap is used when the function you apply already returns an Optional, to avoid ending up with an Optional<Optional<T>>:

java
public Optional<Address> searchAddress(User user) {
    return Optional.ofNullable(user.getAddress());
}
 
Optional<String> city = findUserById("123")
    .flatMap(this::searchAddress)
    .map(Address::getCity);

Executing Actions with ifPresent and ifPresentOrElse

Instead of checking if a value is present and then doing something, you can directly tell Optional what to do in each case:

java
findUserById("123")
    .ifPresent(u -> System.out.println("Welcome, " + u.getName()));

Since Java 9, ifPresentOrElse also exists to handle both cases at once:

java
findUserById("123").ifPresentOrElse(
    u -> System.out.println("Welcome, " + u.getName()),
    () -> System.out.println("User not found")
);

Complete Example

java
public String getBillingCity(String idUser) {
    return findUserById(idUser)
        .filter(User::isActive)
        .flatMap(this::searchAddress)
        .map(Address::getCity)
        .map(String::toUpperCase)
        .orElse("CITY NOT AVAILABLE");
}

Compared to the alternative of nested null checks, the difference is huge. Optional does not eliminate the concept of null, but it does eliminate the ambiguity that exists. It forces you to think about the case where the value does not exist.

If you want to learn Java concepts, you can go to: The basics of Java you need to know.

Frequently asked questions

What is Optional in Java?

Optional<T> is a container that either holds a value of type T or is empty. It forces you to handle the missing-value case explicitly instead of accessing a value that could be null.

Since which Java version does Optional exist?

Java 8. ifPresentOrElse specifically was added later, in Java 9.

What is the difference between orElse and orElseGet?

orElse always evaluates its argument, even when a value is present. orElseGet only runs its supplier if the value is actually missing. If the default value is expensive to compute, like a database call, use orElseGet.

Does Optional remove NullPointerException completely?

No. Optional doesn't eliminate null, it eliminates the ambiguity around it. It forces you to think about the case where a value doesn't exist, instead of hitting an unexpected NullPointerException in production.

Comments