Lesson 23 • Advanced
Working with Optionals
Eliminate null pointer exceptions with Optional chains and patterns.
Before You Start
You should know Generics (Lesson 14) and Lambda Expressions (Lesson 22). Optional uses both extensively — it's a generic container that works best with lambda-based methods like map() and orElseGet().
What You'll Learn
- ✅ Creating Optionals: of(), ofNullable(), empty()
- ✅ Unwrapping: get(), orElse(), orElseGet(), orElseThrow()
- ✅ Chaining: map(), flatMap(), filter()
- ✅ Optional anti-patterns to avoid
- ✅ Optional in method signatures
1️⃣ Why Optional Exists
Tony Hoare called null references his "billion-dollar mistake." NullPointerException is the #1 runtime error in Java. Optional<T> makes absence explicit in the type system — instead of returning null, you return Optional.empty(), forcing callers to handle the missing case.
💡 Analogy: Gift Box
Think of Optional as a gift box. Optional.of(value) is a box with a gift inside — guaranteed. Optional.empty() is an empty box. Optional.ofNullable(value) is a box that might or might not have a gift. Before opening, you check: isPresent() peeks inside, orElse() says "if empty, use this backup gift," and map() wraps the gift differently without opening the box.
Try It: Creating & Unwrapping Optionals
// Optional — Creating & Unwrapping
console.log("=== Optional Basics ===\n");
class Optional {
constructor(value) { this._value = value; }
static of(value) {
if (value == null) throw new Error("Value cannot be null");
return new Optional(value);
}
static ofNullable(value) { return new Optional(value); }
static empty() { return new Optional(null); }
isPresent() { return this._value != null; }
isEmpty() { return this._value == null; }
get() {
...2️⃣ Chaining: map, flatMap & filter
The real power of Optional is chaining. map() transforms the value inside without unwrapping — if the Optional is empty, it stays empty. flatMap() is for when your transformation itself returns an Optional (avoiding Optional<Optional<T>>). filter() converts a present value to empty if it doesn't match a condition.
Try It: Chaining & Safe Nested Access
// Optional Chaining — map, flatMap, filter
console.log("=== Optional Chaining ===\n");
class Optional {
constructor(value) { this._value = value; }
static of(value) { if (value == null) throw new Error("null"); return new Optional(value); }
static ofNullable(value) { return new Optional(value); }
static empty() { return new Optional(null); }
isPresent() { return this._value != null; }
get() { if (!this.isPresent()) throw new Error("No value"); return this._value; }
...3️⃣ When to Use Optional
Use it as a return type for methods that might not have a result: Optional<User> findById(Long id). Don't use it for fields, method parameters, or collections (an empty list is better than Optional<List>). Optional is designed for "might not exist" return values, not as a general null replacement.
Try It: Real-World Optional Patterns
// Real-World Optional Patterns
console.log("=== Optional Patterns ===\n");
class Optional {
constructor(value) { this._value = value; }
static of(v) { if (v == null) throw new Error("null"); return new Optional(v); }
static ofNullable(v) { return new Optional(v); }
static empty() { return new Optional(null); }
isPresent() { return this._value != null; }
orElse(o) { return this.isPresent() ? this._value : o; }
orElseGet(fn) { return this.isPresent() ? this._value : f
...Common Mistakes
optional.get() throws NoSuchElementException if empty. Use orElse(), orElseGet(), or orElseThrow() instead.Optional.ofNullable() when the value might be null.if (opt.isPresent()) opt.get() is just a verbose null check. Use opt.ifPresent(), opt.map(), or opt.orElse() instead.orElse(createExpensiveDefault()) executes always. Use orElseGet(() → createExpensiveDefault()) for lazy evaluation.Pro Tips
💡 Optional.stream() (Java 9+) converts Optional to a Stream of 0 or 1 elements — great for flatMapping a list of Optionals.
💡 or() (Java 9+) chains Optional suppliers: findInCache(id).or(() → findInDB(id)).
💡 Return Optional from repositories, return concrete types from services. The service layer decides what "not found" means.
📋 Quick Reference
| Method | Returns | Use When |
|---|---|---|
| orElse(T) | T | Default is cheap to create |
| orElseGet(Supplier) | T | Default is expensive (lazy) |
| orElseThrow() | T or throws | Missing value is an error |
| map(Function) | Optional<R> | Transform value |
| flatMap(Function) | Optional<R> | Chain Optional-returning fns |
🎉 Lesson Complete!
You can now write null-safe code using Optional chains!
Next: Collections Framework Internals — how ArrayList, LinkedList, and HashSet work under the hood.
Sign up for free to track which lessons you've completed and get learning reminders.