Modern Java

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.

Java 8+ (Optional); Java 21 (core language)

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