Functional Java - Optional
Learn when and how to use Optional in Java for null-safe flows, map/flatMap/orElse patterns, and API boundary best practices.
#java #functional #optional #null-safety
Why this step matters
null is a frequent source of production bugs. Optional helps model absence explicitly and avoid hidden NullPointerException.
Core idea
Optional<T> means: value may or may not be present.
Optional<String> token = Optional.of("abc");
Optional<String> missing = Optional.empty();
Use:
of(...)when value is guaranteed non-nullofNullable(...)when value may be nullempty()for no value
Common operations
Optional<String> email = Optional.ofNullable("briac@example.com");
String domain = email
.map(e -> e.substring(e.indexOf("@") + 1))
.orElse("unknown");
System.out.println(domain); // example.com
map: transform if presentflatMap: chain Optional-returning callsorElse/orElseGet: fallback valuesorElseThrow: fail explicitly
map vs flatMap
The key difference is the return shape.
mapapplies a functionT -> Rand returnsOptional<R>flatMapapplies a functionT -> Optional<R>and returnsOptional<R>(flattened)
If your mapper already returns an Optional, map creates nesting.
Optional<User> user = findUser("briac");
Optional<Optional<String>> wrong = user.map(u -> findCityByUser(u.id()));
Optional<String> correct = user.flatMap(u -> findCityByUser(u.id()));
Why:
- with
map, Java wraps mapper output again, so you getOptional<Optional<String>> - with
flatMap, Java avoids double wrapping and returns a singleOptional<String>
Use map when your mapper returns a plain value:
Optional<User> user = findUser("briac");
Optional<String> username = user.map(User::username);
Use flatMap when your mapper returns an Optional:
Optional<User> user = findUser("briac");
Optional<String> city = user.flatMap(u -> findCityByUser(u.id()));
API boundary best practices
Good:
- return
Optional<T>from query-like methods (findById)
Avoid:
- fields of type
Optionalin entities/DTOs Optionalas method parameter in most cases
orElse vs orElseGet
orElse always evaluates its argument.
orElseGet computes fallback lazily.
String value = optional.orElseGet(() -> expensiveFallback());
Prefer orElseGet when fallback is costly.
Common mistakes
- using
Optional.get()without checking presence - wrapping everything in Optional (over-design)
- returning
nullinstead ofOptional.empty()
Takeaway
- Use Optional to model missing values explicitly
- Compose with
mapandflatMap - Use
orElseGet/orElseThrowintentionally - Keep Optional at API/query boundaries, not everywhere