Optional — Creation, map/flatMap, orElse, ifPresent, and Best Practices
In this tutorial, you will learn about Optional. We cover key concepts, practical examples, and best practices to help you master this topic.
Java Optional is a container object that may or may not contain a non-null value, providing a functional alternative to null checks. Optional<T> forces you to think about the absent case — you cannot simply get the value without explicitly handling the possibility of emptiness.
What You'll Learn
- Creating Optional: empty, of, ofNullable
- Retrieving values: get, orElse, orElseGet, orElseThrow
- Functional operations: map, flatMap, filter, ifPresent
- Best practices and common misuses
Why It Matters
Null references are the source of countless NullPointerExceptions. While Optional does not eliminate null (it is still possible to have a null reference), it makes the absence of a value explicit in the type system and encourages proper handling.
Real-World Use
Spring Data JPA returns Optional from Repository methods. CompletableFuture uses Optional-like patterns. Stream terminal operations like findFirst() return Optional.
Creating Optional
// Empty Optional
Optional<String> empty = Optional.empty();
// Non-null value
Optional<String> value = Optional.of("Hello");
// Throws NullPointerException if argument is null
// Nullable value
Optional<String> nullable = Optional.ofNullable(maybeNull);
// Returns Optional.empty() if argument is null
Retrieving Values
orElse — Provide a Default
String result = optional.orElse("default");
// Returns value if present, otherwise "default"
orElseGet — Lazy Default (Supplier)
String result = optional.orElseGet(() -> expensiveDefault());
// The supplier is only called if the Optional is empty
orElseThrow — Throw Exception on Empty
String result = optional.orElseThrow(() -> new IllegalArgumentException("Missing value"));
get — Direct (Avoid)
String result = optional.get();
// Throws NoSuchElementException if empty — use orElseThrow instead
Functional Operations
map
Optional<String> name = Optional.of("Alice");
Optional<Integer> length = name.map(String::length);
// Optional[5]
flatMap
Used when the mapping function itself returns an Optional:
public Optional<String> findNickname(String name) {
Map<String, String> nicknames = Map.of("Alice", "Ali");
return Optional.ofNullable(nicknames.get(name));
}
Optional<String> name = Optional.of("Alice");
Optional<String> nickname = name.flatMap(this::findNickname);
// Optional[Ali]
filter
Optional<String> name = Optional.of("Alice");
Optional<String> longName = name.filter(s -> s.length() > 3);
// Optional[Alice]
Optional<String> shortName = name.filter(s -> s.length() > 10);
// Optional.empty()
ifPresent
optional.ifPresent(value -> System.out.println("Value: " + value));
ifPresentOrElse (Java 9+)
optional.ifPresentOrElse(
value -> System.out.println("Value: " + value),
() -> System.out.println("No value present")
);
or (Java 9+)
Optional<String> result = optional.or(() -> Optional.of("fallback"));
Stream-like Chaining
Optional supports fluent chaining with map, flatMap, and filter:
public class UserService {
public Optional<String> getCityDisplayName(Long userId) {
return findUser(userId)
.flatMap(this::getAddress)
.map(Address::getCity)
.map(City::getDisplayName);
}
}
Each step automatically propagates emptiness — if any step returns Optional.empty(), the final result is empty without nested null checks.
Optional with Streams
List<Optional<String>> optionals = List.of(
Optional.of("a"),
Optional.empty(),
Optional.of("b")
);
// Java 9+: flatMap(Optional::stream)
List<String> values = optionals.stream()
.flatMap(Optional::stream)
.toList();
// [a, b]
// Pre-Java 9 alternative
List<String> values2 = optionals.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.toList();
Primitive Optional
OptionalInt optionalInt = OptionalInt.of(42);
OptionalLong optionalLong = OptionalLong.of(100L);
OptionalDouble optionalDouble = OptionalDouble.of(3.14);
Common Mistakes
- Using
Optional.get()without checking. Always useorElse,orElseGet, ororElseThrowinstead. - Using Optional as a method parameter.
public void Process(Optional<String> input)— this forces the caller to wrap in Optional. Accept the raw type and handle null at the boundary. - Using Optional for fields. Optional is not Serializable and adds overhead. Use null checks or a default sentinel value for fields.
- Calling
isPresent()thenget()in separate steps. This is the Optional equivalent of a null check — useifPresent,orElse, or map instead. - Returning null from an Optional-returning method. If a method returns
Optional, it should never return null. ReturnOptional.empty()to indicate absence.
Practice Questions
1. What is the difference between Optional.of() and Optional.ofNullable()?
of() requires non-null argument (throws NPE if null). ofNullable() accepts null and returns Optional.empty().
2. What is the difference between orElse and orElseGet?
orElse takes a value (always evaluated). orElseGet takes a Supplier (only called if empty). Use orElseGet when the default is expensive to compute.
3. How does flatMap differ from map on Optional?
map wraps the result in Optional. flatMap expects the mapping function to return an Optional (avoiding nested Optional<Optional<T>>).
4. Why should you not use Optional as a method parameter?
It forces the caller to construct an Optional, adds overhead, and is an anti-pattern. Use method overloading or @Nullable annotations instead.
5. What is the Java 9 or() method?
It returns the Optional if present, otherwise returns the Optional produced by the supplier (similar to orElseGet but returns Optional).
Challenge Question:
Write a UserLookupService with methods Optional<User> findById(Long id), Optional<Email> getEmail(User user), and Optional<String> getDomain(Email email). Chain these to safely get a user's email domain. Then write a method that collects all valid email domains from a list of user IDs, skipping IDs that fail at any step.
FAQ
Mini Project
Write a program OptionalDemo.java that:
- Creates a
UserRepositorywithfindById(long id)returningOptional<User> - Creates a
UserServicewith methods that usemap,flatMap,orElse, andorElseThrow - Demonstrates
orElsevsorElseGetwith a logging side effect - Uses
ifPresentOrElseto handle both present and absent cases - Processes a list of IDs, collecting only those where the user exists and is active (filter)
- Implements a
Configclass that usesOptional<String>for optional configuration values - Shows the
OptionalIntandOptionalDoubleprimitive variants - Demonstrates the anti-pattern of Optional parameters and suggests better alternatives
What's Next
Optional handles synchronous absence of values, but modern applications need asynchronous operations. Lesson 37 covers CompletableFuture — asynchronous programming with supplyAsync, thenCompose, allOf, and Exception Handling.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro