Module 9 · Modern Java: Functional Style & Concurrency ⏱ 19 min

Optional and Null-Safety Practices

By the end of this lesson you will be able to:
  • Create Optional instances safely using of, ofNullable, and empty
  • Chain Optional operations to eliminate nested null checks
  • Recognise when Optional.get() is unsafe and prefer orElse or orElseThrow

Tony Hoare, who invented null references in 1965, later called it his billion-dollar mistake. In Java, null means no object here, and every reference variable can hold it. That flexibility is exactly what makes NullPointerException the most common runtime crash in Java code. You call a method on a variable that holds null, the JVM has no object to invoke the method on, and your program halts.

Defensive programming against null quickly turns into a pyramid of nested if checks. Is the user null? Is the user's address null? Is the address city null? Each layer adds indentation and each layer is a place you can forget. Java 8 introduced Optional<T> to replace that pyramid with a flat, readable chain of operations that explicitly models the possibility of absence.

flowchart TD
  N["name.getCity()"] -->|"null crashes"| E["NullPointerException"]
  P["Nested if checks<br/>if (u != null)<br/>if (a != null)<br/>city = a.getCity()"] -->|"verbose"| R["pyramid of doom"]
  O["Optional chain<br/>.map(...)<br/>.map(...)<br/>.orElse(#quot;Unknown#quot;)"] -->|"declarative"| S["safe flat result"]
  style P fill:#b45309,color:#fff
  style O fill:#3776ab,color:#fff
Nested null checks create a pyramid; Optional chains stay flat and readable.

Creating an Optional

Optional<T> is a container that either holds a non-null value or is empty. There are three ways to build one:

  • Optional.of(value) wraps a value you know is non-null. Hand it null and it throws NullPointerException immediately — a fast fail that surfaces the bug where it was created, not where the value was later used.
  • Optional.ofNullable(value) wraps a value that might be null. If the value is null, you get an empty Optional instead of a crash.
  • Optional.empty() builds an empty Optional directly, useful as a default return when there is genuinely nothing to return.

The first is the most common trap: Optional.of(someValue) looks like the natural choice, but if someValue came from a database, a map lookup, or an external API, it could be null. When in doubt, start with ofNullable.

Main.java — creating Optionals three ways. Real, compiling Java.
import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        String name = "Ada";
        String missing = null;

        Optional<String> sure = Optional.of(name);
        Optional<String> maybe = Optional.ofNullable(missing);
        Optional<String> empty = Optional.empty();

        System.out.println(sure.isPresent());
        System.out.println(maybe.isPresent());
        System.out.println(empty.isPresent());
    }
}

Chaining operations

The power of Optional is not the wrapper itself — it is the operations you can chain on it. Each operation returns another Optional, so you can express a multi-step computation that gracefully handles absence at any point.

  • map(fn) transforms the value if present, returning a new Optional with the result. If the original was empty, map returns Optional.empty() without calling the function.
  • filter(pred) keeps the value only if it matches the predicate; otherwise it returns Optional.empty().
  • orElse(other) unwraps the value if present, or returns other if empty.
  • orElseGet(supplier) is like orElse but only builds the fallback when it is actually needed — important when the fallback is expensive.
  • ifPresent(action) runs a side effect only when a value exists.

These five methods cover most real-world use cases. They let you replace an if (x != null) block with a single expression that says the same thing more explicitly.

flowchart LR
  V["Optional.ofNullable(#quot;42#quot;)"] -->|"map to int"| M["Optional[42]"]
  M -->|"filter > 10"| F["Optional[42]"]
  F -->|"value present"| O["orElse(0) returns 42"]
  F -.->|"if empty"| E["orElse(0) returns 0"]
  style V fill:#3776ab,color:#fff
  style F fill:#c2410c,color:#fff
An Optional pipeline: map transforms, filter guards, and orElse supplies a fallback if the chain empties.
Main.java — chaining map, filter, and orElse. Real, compiling Java.
import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        Optional<String> input = Optional.ofNullable("42");

        int result = input
            .map(s -> Integer.parseInt(s))
            .filter(n -> n > 10)
            .orElse(0);

        System.out.println(result);
    }
}

flatMap: the nested Optional cure

A map that returns an Optional produces Optional<Optional<T>> — a nested container that is awkward to unwrap. flatMap flattens the result for you, returning a single Optional<T>.

This is the tool that replaces the deepest null-check pyramids. Suppose person.getAddress() returns Optional<Address>, and address.getCity() returns Optional<String>. A chain of flatMap lets you move from Optional<Person> to Optional<String> without ever writing if (x != null).

Use Optional as a return type from methods that might not find a result. Do not use it as a field type or a method parameter — that is considered an anti-pattern in Java. Fields should be nullable when genuinely optional; parameters should be required or overloaded. Optional is for return types and local variables, where it signals to the caller that absence is expected and they must handle it.

flowchart TD
  P["Optional Person"] --> M1["map to Address"]
  M1 --> B1["Nested Optional<br/>awkward"]
  P --> M2["flatMap to Address"]
  M2 --> B2["Optional Address<br/>flat single layer"]
  B2 --> M3["flatMap to City"]
  M3 --> B3["Optional String"]
  style B1 fill:#b45309,color:#fff
  style B3 fill:#3776ab,color:#fff
flatMap collapses Optional<Optional<String>> into a single Optional<String>.
Main.java — flatMap collapses nested Optionals into one layer. Real, compiling Java.
import java.util.Optional;

public class Main {
    static class Address {
        Optional<String> getCity() {
            return Optional.of("Paris");
        }
    }

    static class Person {
        Optional<Address> getAddress() {
            return Optional.of(new Address());
        }
    }

    public static void main(String[] args) {
        Optional<Person> person = Optional.of(new Person());

        Optional<String> city = person
            .flatMap(Person::getAddress)
            .flatMap(Address::getCity);

        System.out.println(city.orElse("Unknown"));
    }
}
Exercise

What does this program print? Read it carefully and type the exact output (one line).

import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        String value = null;
        Optional<String> opt = Optional.ofNullable(value);
        System.out.println(opt.orElse("default"));
    }
}
Exercise

What does this program print? Read it carefully and type the exact output (one line).

import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        Optional<String> name = Optional.of("Ada");
        Optional<Integer> length = name.map(s -> s.length());
        System.out.println(length.orElse(0));
    }
}
Exercise

What happens when you call Optional.of(null)?

Exercise

What does this program print? Read it carefully and type the exact output (two lines).

import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        Optional<Integer> num = Optional.of(7);
        System.out.println(num.filter(n -> n > 10).orElse(0));
        System.out.println(num.filter(n -> n > 5).orElse(0));
    }
}
Exercise

Which of the following is considered an anti-pattern when using Optional in Java?

Recap

  • Optional<T> explicitly models the presence or absence of a value, replacing null-check pyramids with flat chains.
  • Optional.of wraps a known non-null value; Optional.ofNullable handles values that might be null; Optional.empty() builds an empty one directly.
  • map transforms a present value; filter keeps it only if a predicate matches; flatMap flattens nested Optionals.
  • orElse, orElseGet, and orElseThrow safely unwrap the value; get() without a guard throws NoSuchElementException and defeats the purpose.
  • Use Optional for return types and local variables, not as fields or parameters.

Next you will meet Java's mechanisms for running work in parallel — threads, executors, and why more workers does not always mean predictable order.

Checkpoint quiz

What does Optional.ofNullable(null) return?

Which method safely returns a default value when an Optional is empty?

Go deeper — technical resources