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:
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:
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.

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:
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:
// 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
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
String name = findUserById("123")
.map(User::getName)
.orElse("Unknown user");
orElseGet: a default value
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
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.
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>>:
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:
findUserById("123")
.ifPresent(u -> System.out.println("Welcome, " + u.getName()));
Since Java 9, ifPresentOrElse also exists to handle both cases at once:
findUserById("123").ifPresentOrElse(
u -> System.out.println("Welcome, " + u.getName()),
() -> System.out.println("User not found")
);
Complete Example
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.