Replacing Null Checks with Java's Optional API
Part 5 of 9 in Functional Java Unleashed
Every Java developer has written it: a cascade of if (x != null) guards that buries the real logic under defensive checks. Java’s Optional<T> was designed exactly to make those patterns more expressive — not as a magic NPE killer, but as a container type that makes absence explicit and composable.
This post walks through the four core patterns: creating Optionals safely from factory methods, transforming values with map/flatMap, providing fallbacks, and wiring Optional into stream pipelines without leaking null checks.
The code
The demo uses a layered domain model (User → Address → Province → Country) where each link may be absent. The same class also provides legacy lookup helpers that return raw null, alongside Optional-friendly versions that wrap absence in Optional.empty().
import java.util.Optional;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class OptionalPatterns {
static record Plan(String tier, String feature) {}
static record Subscription(Plan plan, int yearStart) {}
static record Profile(String bio, Subscription subscription) {}
static record User(String name, User.Address address) {
record Address(String city, User.Province province) {}
record Province(String name, User.Country country) {}
record Country(String name, String code) {}
}
// Legacy lookup — returns null for unknown users.
static Profile legacyLookupProfile(String username) {
if ("bob".equals(username)) return null;
return new Profile("engineer", new Subscription(null, 2024));
}
// Optional-friendly equivalent.
static Optional<Subscription> lookupSubscriptionV2(String username) {
return "bob".equals(username)
? Optional.empty()
: Optional.ofNullable(legacyLookupProfile(username))
.map(p -> p.subscription());
}
// Chained: user → subscription → plan, fully optional.
static Optional<Plan> lookupPlan(String username) {
return lookupSubscriptionV2(username)
.map(s -> Optional.ofNullable(s.plan()))
.orElse(Optional.empty());
}
static void demoFactories() {
// of() — value guaranteed non-null; throws NPE otherwise
var sure = Optional.of("hello");
// empty() — the canonical absent sentinel
var absent = Optional.<String>empty();
// ofNullable() — bridges the null world safely
String nullable = null;
var safe = Optional.ofNullable(nullable);
}
static void demoTransformations() {
// map: transform the contained value; no-op on empty
var upper = Optional.of("alice").map(String::toUpperCase);
// flatMap: when the transform itself returns an Optional
var addr = Optional.of(newUser())
.flatMap(u -> Optional.ofNullable(u.address()));
}
static void demoFallbacks() {
var absent = Optional.<String>empty();
var present = Optional.of("hello");
// orElse — eager default (always evaluated)
var fallback1 = absent.orElse("default");
// orElseGet — lazy default via supplier (only called when empty)
Supplier<String> gen = () -> "generated";
var fallback2 = absent.orElseGet(gen);
// orElseThrow — convert absence to error
absent.orElseThrow(() -> new IllegalArgumentException("Missing"));
// ifPresent — side-effect only when present
present.ifPresent(v -> System.out.println(v));
}
static void demoStreamPipelines() {
List<String> usernames = List.of("alice", "bob", "carol");
var plans = usernames.stream()
.map(OptionalPatterns::lookupPlan) // Optional<Plan>
.filter(Optional::isPresent)
.map(Optional::get)
.map(pl -> String.format("%s (%s)", pl.tier(), pl.feature()))
.collect(Collectors.toList());
}
}
Running it
The four demo methods cover each pattern section and produce this output:
=== 1. Factory methods ===
of("hello") -> Optional[hello]
empty() -> Optional.empty (isPresent: false)
ofNullable(null) -> Optional.empty
ofNullable("world") -> Optional[world]
=== 2. map and flatMap ===
map(upper): Optional[alice] -> Optional[ALICE]
map on empty: Optional.empty -> Optional.empty
(notPresent: true)
flatMap address: Optional.empty
Deep chain: Optional[Country[name=USA, code=US]]
Country name: Optional[USA]
=== 3. Fallback strategies ===
orElse("default") -> default string
orElseGet on empty: generated (supplier called 1 time(s))
orElseGet on present: existing value (supplier called 0 time(s))
orElseThrow: caught IllegalArgumentException: User not found
ifPresent: 'existing value'
(no output above = correct behavior for empty)
bare orElseThrow: caught NoSuchElementException
=== 4. Stream pipeline integration ===
Users with valid addr: 2 / 4
City names: [Boston, SF]
Native-Optional stream approach:
Valid plans (v2): 2 / 3
Plan details: [premium (analytics), premium (analytics)]