Optional
IntermediateUse Optional<T> as an explicit nullable return type — eliminating null checks and NullPointerExceptions with a rich functional API.
Overview
Optional<T> is a container that either holds a value (non-null) or is empty. It forces callers to explicitly deal with the absent-value case, making the possibility of missing data visible in the type system. Optional should be used as a method return type — not as a field type, parameter type, or collection element. The functional API (map, flatMap, filter, ifPresent) lets you chain operations on the value without explicit null checks.
Creating & Consuming Optional
Three factory methods: Optional.of(value) — throws NullPointerException if value is null Optional.ofNullable(value) — empty if null, present otherwise Optional.empty() — explicitly absent
Consuming methods: isPresent() / isEmpty() (Java 11+) — check presence get() — get value, throws NoSuchElementException if empty — avoid! orElse(default) — return value or default orElseGet(Supplier) — lazy default — prefer over orElse for expensive defaults orElseThrow(Supplier) — throw custom exception if empty ifPresent(Consumer) — act only if value present ifPresentOrElse(Consumer, Runnable) — (Java 9+)
import java.util.Optional;
public class OptionalBasics {
static Optional<String> findUserEmail(int id) {
if (id == 1) return Optional.of("alice@example.com");
if (id == 2) return Optional.ofNullable(null); // same as Optional.empty()
return Optional.empty();
}
public static void main(String[] args) {
Optional<String> email = findUserEmail(1);
// isPresent / get — verbose, avoid get() without check
if (email.isPresent()) System.out.println(email.get()); // alice@example.com
// orElse — return default if empty
System.out.println(findUserEmail(2).orElse("no-reply@example.com"));
// orElseGet — lazy supplier (only called if empty)
System.out.println(findUserEmail(99).orElseGet(() -> "generated@example.com"));
// orElseThrow — throw if empty
try {
findUserEmail(99).orElseThrow(() ->
new IllegalArgumentException("User not found"));
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // User not found
}
// ifPresent — side-effect only if present
findUserEmail(1).ifPresent(e -> System.out.println("Sending to: " + e));
// ifPresentOrElse (Java 9+)
findUserEmail(99).ifPresentOrElse(
e -> System.out.println("Email: " + e),
() -> System.out.println("No email found"));
}
}map, flatMap & filter
Optional has the same map/flatMap/filter as Stream, but for a single element:
map(Function) — transform the value if present, otherwise stays empty flatMap(Function<T, Optional<R>>) — use when the mapper itself returns Optional (avoids Optional<Optional<T>>) filter(Predicate) — if present but fails predicate, returns empty
These let you chain nullable operations without ever writing != null.
import java.util.Optional;
public class OptionalChaining {
record User(String name, Optional<Address> address) {}
record Address(String city, Optional<String> zip) {}
static Optional<User> findUser(int id) {
if (id == 1) return Optional.of(
new User("Alice", Optional.of(new Address("NYC", Optional.of("10001")))));
if (id == 2) return Optional.of(
new User("Bob", Optional.empty()));
return Optional.empty();
}
public static void main(String[] args) {
// Chained map — no null checks needed
String city = findUser(1)
.flatMap(User::address)
.map(Address::city)
.orElse("Unknown city");
System.out.println(city); // NYC
String city2 = findUser(2)
.flatMap(User::address)
.map(Address::city)
.orElse("Unknown city");
System.out.println(city2); // Unknown city
String city3 = findUser(99)
.flatMap(User::address)
.map(Address::city)
.orElse("Unknown city");
System.out.println(city3); // Unknown city
// filter — stays present only if predicate passes
Optional<String> longCity = findUser(1)
.flatMap(User::address)
.map(Address::city)
.filter(c -> c.length() > 2);
System.out.println(longCity); // Optional[NYC]
// map vs flatMap — avoid Optional<Optional<T>>
Optional<String> email = Optional.of(" alice@example.com ");
// map returns Optional<String>
Optional<String> trimmed = email.map(String::trim);
System.out.println(trimmed); // Optional[alice@example.com]
}
}Optional Best Practices
DO: • Use Optional as a method return type to signal that a value might be absent • Prefer orElseGet over orElse when the default is expensive to compute • Chain with map/flatMap instead of isPresent() + get()
DO NOT: • Use Optional as a field type — breaks serialisation, adds memory overhead • Use Optional as a parameter type — callers can pass null Optional • Put Optional in collections — use empty collection or filter nulls instead • Call get() without an isPresent() check — defeats the purpose
import java.util.*;
import java.util.stream.Stream;
public class OptionalBestPractices {
// GOOD: Optional as return type
static Optional<String> lookupCode(String country) {
Map<String, String> codes = Map.of("US", "+1", "IN", "+91");
return Optional.ofNullable(codes.get(country));
}
// BAD: Optional as parameter — callers can pass null
// static void process(Optional<String> name) { ... }
// GOOD: use overloading or null check
static void process(String name) {
Objects.requireNonNull(name);
System.out.println("Processing: " + name);
}
public static void main(String[] args) {
// orElseGet — supplier only called when empty
String code = lookupCode("IN").orElseGet(() -> {
System.out.println("Computing default..."); // not called for "IN"
return "unknown";
});
System.out.println(code); // +91
String code2 = lookupCode("XX").orElseGet(() -> {
System.out.println("Computing default..."); // called
return "unknown";
});
System.out.println(code2); // unknown
// Optional.stream() (Java 9+) — integrate with Stream pipelines
List<String> countries = List.of("US", "XX", "IN", "YY");
List<String> foundCodes = countries.stream()
.map(OptionalBestPractices::lookupCode)
.flatMap(Optional::stream) // discard empties, unwrap presents
.collect(java.util.stream.Collectors.toList());
System.out.println(foundCodes); // [+1, +91]
}
}Key Points to Remember
- Optional.of() throws NPE on null; ofNullable() handles null; empty() is explicitly absent
- Prefer orElseGet(Supplier) over orElse(value) — the supplier is lazy, value is always evaluated
- Use map/flatMap/filter to chain Optional operations without if-else null checks
- flatMap prevents Optional<Optional<T>> when the mapper itself returns Optional
- Optional.stream() (Java 9+) bridges Optional into Stream pipelines cleanly
- Never use Optional as a field, parameter, or collection element — only as a return type
Practice Optional in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat is the purpose of Optional in Java?
What is the difference between orElse() and orElseGet()?
What is the difference between Optional.map() and Optional.flatMap()?
Should you use Optional as a method parameter? Why or why not?
How does Optional.stream() work in Java 9+?
Ask Aria about Optional
Your personal AI tutor — ask anything about this concept