DevNotes
Support
Java 8 & Streams · Chapter 12

Optional

Making absence explicit — of vs ofNullable, why get() is dangerous, ifPresent, orElse vs orElseGet, orElseThrow, and map/filter/flatMap on Optional.

◷ 5 min read§ 12 sections? 5 interview questions{ } 21 code examples

#Introduction

If there is one Java 8 feature that interviewers love after Streams, it's Optional.

#Why Was Optional Introduced?

Before Java 8:

Java
User user = repository.findById(id);

if (user != null) {
    System.out.println(user.getName());
}

The problem: NullPointerException. Millions of production bugs happen because of null. Java 8 introduced Optional to make the absence of a value explicit.

Think of it as a box:

Optional<User>
Does the box contain a value?
User
Optional.empty()

#Creating an Optional

Optional.of()

Java
String name = "John";
Optional<String> optional = Optional.of(name);

If name is null, Optional.of(null) throws NullPointerException. Use it only when you're sure the value isn't null.

Optional.ofNullable()

Java
String name = null;

Optional<String> optional = Optional.ofNullable(name);
System.out.println(optional);
Output
Optional.empty

This is the method you'll use most often.

Optional.empty()

Java
Optional<String> optional = Optional.empty();

An empty Optional.

#Reading the Value

isPresent()

Java
Optional<String> name = Optional.of("John");

if (name.isPresent()) {
    System.out.println(name.get());
}
Output
John

Why get() is Dangerous

Java
Optional<String> name = Optional.empty();

name.get();
Output
NoSuchElementException: No value present

ifPresent()

Instead of:

Java
if (optional.isPresent()) {
    System.out.println(optional.get());
}

use:

Java
optional.ifPresent(System.out::println);

Cleaner and safer.

#Default Values

orElse()

Return a default value.

Java
Optional<String> name = Optional.empty();

String result = name.orElse("Guest");
System.out.println(result);
Output
Guest

orElseGet()

Looks similar, but is different:

Java
String result = optional.orElseGet(() -> "Guest");

Why? Because the supplier is executed only if needed.

#orElse() vs orElseGet() — Proof

A classic interview question. Let's prove the difference.

Java
public static String expensiveMethod() {
    System.out.println("Executing...");
    return "Guest";
}

Case 1 — orElse():

Java
Optional<String> optional = Optional.of("John");

System.out.println(optional.orElse(expensiveMethod()));
Output
Executing...
John

Wait… why did expensiveMethod() execute? Because orElse() evaluates its argument before it checks whether the Optional contains a value — it's an ordinary method argument.

Case 2 — orElseGet():

Java
Optional<String> optional = Optional.of("John");

System.out.println(optional.orElseGet(() -> expensiveMethod()));
Output
John

No "Executing...". The supplier wasn't called because the value was already present.

The Rule

Use orElse() forUse orElseGet() for
ConstantsExpensive database calls
Simple default values, e.g. .orElse("Guest")API calls
Object creation
Complex logic

#orElseThrow()

Suppose a user must exist:

Java
User user = repository.findById(id)
        .orElseThrow();

If not found → NoSuchElementException.

With a custom exception:

Java
User user = repository.findById(id)
        .orElseThrow(() -> new RuntimeException("User not found"));

Very common in Spring Boot services.

#Transforming an Optional

map()

Java
Optional<Employee> employee = repository.findById(1);

Need the employee name. Instead of:

Java
if (employee.isPresent()) {
    return employee.get().getName();
}

use:

Java
String name = employee
        .map(Employee::getName)
        .orElse("Unknown");

Beautiful.

filter()

Java
Optional<Employee> employee = repository.findById(1);

employee
        .filter(e -> e.getSalary() > 50000)
        .ifPresent(System.out::println);

flatMap()

Suppose Optional<User> where User.getAddress() returns Optional<Address>.

Optional<User>
Optional<Optional<Address>>nested
Optional<User>
Optional<Address>flat
Just like Streams, flatMap() avoids nested containers
Java
Optional<Address> address = user.flatMap(User::getAddress);

#Real Spring Boot Example

Java
public UserDTO getUser(Long id) {
    return repository.findById(id)
            .map(UserMapper::toDto)
            .orElseThrow(() -> new UserNotFoundException(id));
}

This is production-quality code.

#Interview Questions

Q1Difference between Optional.of() and Optional.ofNullable()?
  • of() never accepts null (throws NullPointerException).
  • ofNullable() accepts null and returns Optional.empty().
Q2Difference between orElse() and orElseGet()?
  • orElse() eagerly evaluates its argument.
  • orElseGet() lazily invokes the supplier only when needed.
Q3Why avoid Optional.get()?

Because it throws NoSuchElementException when the Optional is empty.

Q4Can an Optional be null?

Technically yes, but you should never make an Optional variable itself null. An empty Optional already represents "no value."

Q5Where is Optional used most?
  • Repository methods like findById()
  • Service-layer transformations
  • Null-safe chaining
  • Stream terminal operations like findFirst(), max(), min()

#Practice Problems

  1. Create an Optional from a nullable string.
  2. Print the value using ifPresent().
  3. Return "Unknown" if the value is absent.
  4. Use orElseGet() with an expensive method.
  5. Throw a custom exception with orElseThrow().
  6. Convert Optional<Employee> to Optional<String> using map().
  7. Filter employees whose salary is greater than 50,000.
  8. Chain map() and filter() together.
  9. Use flatMap() with nested Optionals.
  10. Rewrite legacy null checks using Optional.

#Chapter Summary

  • Optional.of(), ofNullable(), empty()
  • Why get() is dangerous; prefer ifPresent()
  • orElse() (eager) vs orElseGet() (lazy)
  • orElseThrow() with a custom exception
  • map(), filter(), flatMap() on Optional
  • The Spring Boot findById().map().orElseThrow() pattern