λ Lambdas & Functional Java · Intermediate

Optional in Java

Representing absence: map, flatMap, orElse vs orElseGet, orElseThrow; not for fields or parameters.

🧩 The mysteryfindUser(42) returns null, nobody checks, and production goes down with a NullPointerException. What if the return type itself shouted "this might be empty!"?

A box that might be empty

**Optional<T> either holds a value or is empty. It's designed as a return type** for "there might be no result", forcing callers to deal with absence. Create one with Optional.of(v), Optional.ofNullable(v) or Optional.empty(). **Optional.of(null) throws NullPointerException.**

Optional<User> findUser(int id) { ... }
Optional.of("ada");         // has a value
Optional.ofNullable(null);  // empty, safely
Optional.empty();           // empty

map, then unwrap

**map transforms the value only if present; on an empty Optional it does nothing. Then unwrap: orElse(x)** gives a fallback. Avoid bare get(): prefer **orElseThrow()**, which makes the "must exist" intent explicit.

String name = findUser(42)
    .map(User::name)
    .orElse("guest");
🔮 Predict it

Your turn

What does this print?

var some = Optional.of("java");
var none = Optional.<String>empty();
IO.println(some.map(String::length).orElse(0));
IO.println(none.map(String::length).orElse(0));
  1. 4 0
  2. 4 null
  3. java 0
Show the answer

some holds "java", so map gives Optional[4] → 4. On none, map does nothing and orElse supplies 0.

flatMap for Optional-returning functions

If the function already returns an Optional, map would wrap it again: Optional<Optional<String>>. **flatMap** avoids the nesting.

// User.email() returns Optional<String>
Optional<String> email =
    findUser(id).flatMap(User::email);
// .map(User::email) would give
// Optional<Optional<String>>
🔮 Predict it

Empty, then demanding

What happens?

String s = null;
Optional<String> o = Optional.ofNullable(s);
System.out.println(o.isPresent());
System.out.println(o.orElseThrow());
  1. Prints false, then throws NoSuchElementException
  2. Prints false, then null
  3. Throws NullPointerException at ofNullable
Show the answer

ofNullable(null) safely makes an empty Optional, so isPresent() is false. Then **orElseThrow() on an empty Optional throws NoSuchElementException**.

🔮 Predict it

orElse vs orElseGet

The Optional has a value. What does this print?

static String fallback() {
    System.out.println("computing");
    return "anon";
}
void main() {
    var name = Optional.of("ada");
    IO.println(name.orElse(fallback()));
    IO.println(name.orElseGet(() -> fallback()));
}
  1. ada ada
  2. computing ada ada
  3. computing ada computing ada
Show the answer

orElse(fallback()) is a normal argument, so **fallback() runs first, even though the value is present**. orElseGet takes a Supplier and only calls it when empty. So "computing" prints once.

Where Optional belongs

✗ Misused
class User {
    Optional<String> nickname; // field
}
void greet(Optional<String> name) { }

Fields and parameters: extra allocation, not Serializable, and an Optional parameter can itself be null.

✓ As intended
Optional<String> findNickname(User u) {
    return Optional.ofNullable(u.nick);
}

Return values that might be absent. For fields and parameters, use null checks or overloads.

💼 In the real world

On the job

Spring Data repositories return Optional<T> from findById, and many teams require it for any lookup that can miss. The orElse vs orElseGet trap is a real performance bug: an expensive database call or object creation running on every lookup, even on hits.

Key takeaways

  1. Create: Optional.of(v), Optional.ofNullable(v), Optional.empty()
  2. map transforms the value; flatMap for functions that return Optional
  3. orElse(x) always evaluates x; orElseGet(supplier) only when empty
  4. Avoid get() — prefer orElseThrow() to make the intent explicit

💡 Optional is a gift box that might be empty — you check before you promise someone a present.

🤯 Did you know?

Optional arrived in Java 8, but the handy isEmpty() method only came in Java 11. Before that, you had to write !opt.isPresent().

Practice questions

What does this print?

Function<String, Integer> len = String::length;
var some = Optional.of("ada");
var none = Optional.<String>empty();
System.out.println(some.map(len).orElse(-1));
System.out.println(none.map(len).orElse(-1));
  1. 3 -1
  2. 3 0
  3. ada -1
  4. 3 null
Check your answer

3 -1. map applies the function only when a value is present: Optional[3] → 3. On an empty Optional, map does nothing and orElse supplies -1.

findUser returns Optional<User>, and User.email() returns Optional<String>. Which method gives an Optional<String>?

Optional<String> email =
    findUser(id).___(User::email);
  1. map
  2. flatMap
  3. filter
  4. or
Check your answer

flatMap. flatMap is for functions that already return an Optional — it avoids nesting. map would produce an Optional<Optional<String>>.

Next: try calling a method that throws IOException inside a lambda. The compiler won't let you, even if it never actually throws.