00:00

Optional Class in Java – Handling Absent Values Safely

java.util.Optional<T>, introduced in Java 8, is a container that either holds a value or is empty. Its main purpose is to be a method return type that clearly says "there might be no result", instead of returning null and leaving callers to remember a null check. It comes with a fluent API for supplying defaults, transforming values and throwing exceptions when a value is missing.

This tutorial covers creating and reading optionals, chaining with map, flatMap and filter, the important difference between orElse and orElseGet, and best practices.

1. Creating and Reading an Optional

OptionalBasics.java
import java.util.*;

public class OptionalBasics {
    static final Map<Integer, String> USERS = Map.of(1, "asha", 2, "ravi");

    // The return type tells callers: "there may be no user"
    static Optional<String> findUser(int id) {
        return Optional.ofNullable(USERS.get(id));
    }

    public static void main(String[] args) {
        Optional<String> found = findUser(1);
        Optional<String> missing = findUser(9);

        System.out.println("isPresent      : " + found.isPresent() + " / " + missing.isPresent());
        System.out.println("isEmpty        : " + missing.isEmpty());                       // Java 11
        System.out.println("orElse         : " + missing.orElse("guest"));
        System.out.println("orElseGet      : " + missing.orElseGet(() -> "generated-" + 42));
        found.ifPresent(u -> System.out.println("ifPresent      : hello " + u));
        missing.ifPresentOrElse(u -> System.out.println(u),
                                () -> System.out.println("ifPresentOrElse: no user"));   // Java 9

        try {
            missing.orElseThrow(() -> new NoSuchElementException("user 9 not found"));
        } catch (NoSuchElementException e) {
            System.out.println("orElseThrow    : " + e.getMessage());
        }

        System.out.println("Optional.of    : " + Optional.of("x"));
        System.out.println("Optional.empty : " + Optional.empty());
        try {
            Optional.of(null);
        } catch (NullPointerException e) {
            System.out.println("Optional.of(null) throws NullPointerException");
        }
    }
}
Output
isPresent      : true / false
isEmpty        : true
orElse         : guest
orElseGet      : generated-42
ifPresent      : hello asha
ifPresentOrElse: no user
orElseThrow    : user 9 not found
Optional.of    : Optional[x]
Optional.empty : Optional.empty
Optional.of(null) throws NullPointerException
MethodPurpose
Optional.of(v)Non-null value (throws NullPointerException for null)
Optional.ofNullable(v)Value or empty if null
Optional.empty()Empty optional
isPresent(), isEmpty() (11)Test for a value
orElse(x), orElseGet(supplier)Value or a default
orElseThrow() (10), orElseThrow(supplier)Value or an exception
ifPresent(action), ifPresentOrElse(action, emptyAction) (9)Run code depending on presence
map, flatMap, filterTransform or test the value
or(supplier) (9), stream() (9)Alternative optional; zero-or-one stream

Avoid calling get() without checking: on an empty optional it throws NoSuchElementException, just as risky as a null. Prefer the methods above.

2. Chaining with map, flatMap and filter

Chains replace nested null checks. Each step runs only if a value is present; otherwise the empty optional flows through to the default:

OptionalChaining.java
import java.util.Optional;

public class OptionalChaining {
    record Address(String city, String pincode) { }
    record Customer(String name, Address address) {
        Optional<Address> addressOpt() { return Optional.ofNullable(address); }
    }

    static String cityOf(Customer c) {
        // No nested null checks: map / flatMap / filter / orElse
        return Optional.ofNullable(c)
                .flatMap(Customer::addressOpt)
                .map(Address::city)
                .filter(city -> !city.isBlank())
                .map(String::toUpperCase)
                .orElse("UNKNOWN");
    }

    public static void main(String[] args) {
        System.out.println(cityOf(new Customer("Asha", new Address("Pune", "411001"))));
        System.out.println(cityOf(new Customer("Ravi", null)));
        System.out.println(cityOf(new Customer("Meera", new Address(" ", "000000"))));
        System.out.println(cityOf(null));
    }
}
Output
PUNE
UNKNOWN
UNKNOWN
UNKNOWN
  • map transforms the value (a null result becomes empty).
  • flatMap is used when the function itself returns an Optional, avoiding Optional<Optional<T>>.
  • filter keeps the value only if it matches the predicate.

3. orElse vs orElseGet

OrElseVsOrElseGet.java
import java.util.Optional;

public class OrElseVsOrElseGet {
    static String expensiveDefault() {
        System.out.println("  (expensiveDefault() called)");
        return "default";
    }

    public static void main(String[] args) {
        Optional<String> value = Optional.of("cached");
        System.out.println("orElse:");
        System.out.println("  result = " + value.orElse(expensiveDefault()));      // ALWAYS evaluated
        System.out.println("orElseGet:");
        System.out.println("  result = " + value.orElseGet(OrElseVsOrElseGet::expensiveDefault)); // lazy

        // Optional with streams
        System.out.println("stream count: " + Optional.of("x").stream().count() + ", " + Optional.empty().stream().count());
        System.out.println("or(): " + Optional.<String>empty().or(() -> Optional.of("backup")).get());
    }
}
Output
orElse:
  (expensiveDefault() called)
  result = cached
orElseGet:
  result = cached
stream count: 1, 0
or(): backup

The argument of orElse is evaluated even when the value is present. Use orElseGet when the default is expensive to create or has side effects, such as a database call; use orElse for constants.

4. Best Practices

  • Do use Optional as a return type for methods that may have no result, like findById (Spring Data repositories do exactly this).
  • Do not use it for fields, method parameters, or collection elements; it adds overhead and is not serializable.
  • Never return null from a method declared to return Optional.
  • Return an empty collection rather than Optional<List<T>>.
  • Prefer orElseThrow, orElse and map over isPresent() + get().
  • For primitives, use OptionalInt, OptionalLong and OptionalDouble, as returned by IntStream.max() and similar.

5. Summary

  • Optional is a container for a value that may be absent, designed mainly for return types.
  • Create it with of, ofNullable or empty.
  • Read it with orElse, orElseGet, orElseThrow and ifPresent; transform with map, flatMap and filter.
  • orElse always evaluates its argument; orElseGet is lazy.
  • Avoid get() without checks, and avoid Optional fields and parameters.

Related Tutorials