Eliminate NullPointerException with Optional — the right way to express absent values.
Published February 8, 2025
Optional<T> is a container that may or may not contain a non-null value. It forces callers to handle the case where a value might be absent.
// Old way — null checks everywhere, easy to forget one
User user = findUser(id);
if (user != null) {
Address addr = user.getAddress();
if (addr != null) {
String city = addr.getCity();
}
}
Optional<String> present = Optional.of("hello"); // non-null value
Optional<String> maybe = Optional.ofNullable(nullableValue); // may be null
Optional<String> empty = Optional.empty();
Optional<User> optUser = findUser(id);
// Safe get with default
User user = optUser.orElse(User.ANONYMOUS);
User user = optUser.orElseGet(() -> createDefaultUser()); // lazy
// Throw if absent
User user = optUser.orElseThrow(() -> new UserNotFoundException(id));
// Execute if present
optUser.ifPresent(u -> System.out.println(u.getName()));
// Transform if present
Optional<String> name = optUser.map(User::getName);
// Chain — flatMap avoids Optional<Optional<T>>
Optional<String> city = optUser
.flatMap(User::getAddress)
.map(Address::getCity);
// ❌ Don't call get() without isPresent() check
optUser.get(); // throws NoSuchElementException if empty
// ❌ Don't use Optional as a method parameter
void process(Optional<User> user) {} // use overloads instead
// ❌ Don't use Optional for fields
private Optional<String> name; // just use @Nullable String name
Optional is primarily for return types of methods that may or may not have a result. The goal is to make the absence of a value explicit in the API rather than a hidden null contract.