To understand why Optional was born, you first need to understand NPE. Many object-oriented languages have a null value — a null pointer, pointing from the stack at a heap address that doesn’t exist. When you use an object that points at a nonexistent heap address, a NullPointerException is thrown. If you haven’t anticipated the null case well enough, your program will crash.
Someone finally got fed up with this headache and invented Optional to solve NPE.
Don’t Abuse Optional
Before learning Optional, we need to understand the idea behind how it solves NPE. Don’t abuse Optional — abusing it creates a chaotic mess and makes your code more complex and harder to control. Let’s start with the thinking behind it.
The first thing to be clear about: Optional is a container for holding things. It’s a way of thinking about null pointers. Adopt that thinking and you can solve NPE elegantly. Miss the thinking and you can still cause NPE even with Optional in hand.
Don’t Overuse Optional
You learn something new, think it’s amazing, want to apply it everywhere, feel like refactoring every single class — and end up writing code like this:
String userName = Optional.ofNullable(name).orElse("")
There’s nothing wrong with it, Optional is used correctly, but what’s the point? It hurts readability, allocates an Optional object, and wastes CPU cycles. A ternary expression does the job fine.
Don’t Use Optional as a Bean Field
Optional doesn’t implement Serializable, so using it as a field can break your system. Optional isn’t meant to be a class member. The right way to use it is described below.
Don’t Use Optional as a Setter Parameter
Beyond being non-serializable, it hurts readability. A Setter assigns a value to a field — as the one doing the assigning, don’t you know whether the value is null? So taking an Optional as an assignment parameter is meaningless.
Don’t Use Optional in Collections
Don’t put Optional inside List, Set, Map or other collections. Same story: it’s meaningless. What problem are you trying to solve?
By the same logic, don’t wrap a container type in an Optional. Containers usually have their own null-handling design already. Don’t gild the lily.
Never Assign null to an Optional
Never assign null to an Optional. Use Optional.empty() to assign and express emptiness.
Be Careful with get()
Calling get() without first checking whether the Optional is empty defeats the entire purpose. If you’re not sure a value is present, never call get().
Using Optional Correctly
We covered what not to do. Now let’s look at scenarios where Optional is the right tool.
When you can’t be sure whether a value you received is null, wrap it in Optional and then deal with it.
Use It in Bean Getters
We said above that using Optional in a Setter is forbidden, because assignment is something you actively call — you should know whether the value is null. By that same reasoning, a Getter can return an Optional wrapper, because when reading a value you can’t know whether it’s null, so wrapping it in Optional is exactly what you need.
Elegantly handling NPE in complex objects
Above we gave an example of abusing Optional in simple logic:
String userName = Optional.ofNullable(name).orElse("")
The correct use case is reading values out of complex objects, where Optional earns its keep. Suppose we have a User object with a BaseInfo member, and BaseInfo has an Email property. We don’t know whether User is null, or whether BaseInfo is null, and we want its Email. This brings together several ideas:
-
Use Optional in Bean getters, which makes chained calls possible
-
Use the
::keyword — you can use a lambda expression instead -
Use
Optional.flatMap()to convert types
We can write it like this:
OptionalExample — the code is available at https://example.renfei.net/java/OptionalExample/
package net.renfei;
import java.util.Optional;
public class OptionalExample {
public static class User {
private BaseInfo baseInfo;
public void setBaseInfo(BaseInfo baseInfo) {
this.baseInfo = baseInfo;
}
public Optional<BaseInfo> getBaseInfo() {
return Optional.ofNullable(this.baseInfo);
}
public static class BaseInfo {
private String email;
public void setEmail(String email) {
this.email = email;
}
public Optional<String> getEmail() {
return Optional.ofNullable(this.email);
}
}
}
public static void main(String[] args) {
// Verify the case where user is null
printEmail(null);
// Verify the case where User.BaseInfo is null
User user = new User();
user.setBaseInfo(null);
printEmail(user);
// Verify the case where User.BaseInfo.Email is null
User.BaseInfo baseInfo = new User.BaseInfo();
baseInfo.setEmail(null);
user.setBaseInfo(baseInfo);
printEmail(user);
// Verify the normal case
baseInfo.setEmail("i@renfei.net");
user.setBaseInfo(baseInfo);
printEmail(user);
}
public static void printEmail(User user) {
String defaultEmail = "Unknown Email";
// user comes from outside; we don't know whether it's null
Optional<User> optionalUser = Optional.ofNullable(user);
// We need to print the User's Email; if null, print the default
System.out.println(optionalUser
.flatMap(User::getBaseInfo)
.flatMap(User.BaseInfo::getEmail)
.orElse(defaultEmail));
}
}
As the code above demonstrates, no NPE is thrown when any User member or property is null — the default email is printed every time. Does that give you any ideas?
Wrapping Up
Beyond the short demo above, Optional provides many more methods. Combined with functional-style chaining, it can dispatch NPE problems with style and elegance. This isn’t a pure tutorial — I want to make you think. Finding the line of reasoning matters most, because if you want to learn the API you’re better off reading the official docs; what I write isn’t authoritative. Readers should study and reflect on how the JDK authors think about solving problems, then apply that in their own projects.
