42. Optional โ Best Practices
Optional<T> is a container for a possibly-absent value; use it as a return type to signal absence without null, but avoid it as a field type, parameter type, or in collections.
Explanation
Optional was introduced to reduce NullPointerException by making absence explicit in the method signature. Correct use: return Optional<T> from methods where the result might be absent (like a database lookup or a search). Callers are forced to handle the absent case.
Anti-patterns: using Optional as a method parameter (just use null or two overloads), as a field (wastes heap, use null internally), in collections (List<Optional<T>> โ just use List<T> with filtering), or calling .get() without isPresent() first (defeats the purpose โ use orElse, ifPresent, orElseThrow).
Key API: .orElse(default) returns default if empty; .orElseGet(supplier) lazily computes default; .orElseThrow() throws NoSuchElementException; .map(fn) transforms if present; .flatMap(fn) for operations that also return Optional; .ifPresent(consumer) acts if present; .filter(pred) returns empty if predicate fails.
Example
import java.util.List;
import java.util.Optional;
public class OptionalBestPractices {
record User(long id, String name, String email) {}
// GOOD: return Optional when result might be absent
static Optional<User> findById(long id, List<User> users) {
return users.stream().filter(u -> u.id() == id).findFirst();
}
// BAD: Optional as parameter (use overloads or null instead)
// static void sendEmail(Optional<String> email) { ... }
public static void main(String[] args) {
var users = List.of(
new User(1, "Alice", "alice@example.com"),
new User(2, "Bob", null)
);
// orElse: always evaluated (eager)
String name1 = findById(1, users).map(User::name).orElse("Anonymous");
System.out.println(name1); // Alice
// orElseGet: lazy (supplier only called if empty)
String name2 = findById(99, users).map(User::name).orElseGet(() -> "Unknown");
System.out.println(name2); // Unknown
// orElseThrow: throw when absence is a programming error
try {
findById(99, users).orElseThrow(() -> new RuntimeException("User not found"));
} catch (RuntimeException e) {
System.out.println("Caught: " + e.getMessage());
}
// ifPresent: act on value without extracting it
findById(1, users).ifPresent(u -> System.out.println("Found: " + u.name()));
// filter + map chaining
Optional<String> domain = findById(2, users)
.map(User::email)
.filter(e -> e != null && e.contains("@"))
.map(e -> e.split("@")[1]);
System.out.println(domain.orElse("no email")); // no email (email was null)
}
}Sources
- https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html โ Optional API
- https://www.pearson.com/en-us/subject-catalog/p/effective-java/P200000000138 โ Effective Java, Item 55