⚡IntermediateFunctional Programming

Optional

Use Optional<T> to represent a value that may or may not be present, eliminating null checks and NullPointerExceptions.

Overview

Optional<T> is a container that either holds a value or is empty. It was introduced in Java 8 to make the absence of a value explicit in the API rather than relying on null, which often leads to NullPointerException. Instead of returning null from a method, return Optional.empty(). Instead of returning a value, return Optional.of(value). Callers are then forced by the type system to consider the empty case. Optional works especially well as a return type for search and lookup methods. It should not be used as a method parameter or stored in a field.

Key Points

  • •Optional.of(value) creates an Optional with a non-null value
  • •Optional.ofNullable(value) creates an Optional from a value that might be null
  • •Optional.empty() creates an empty Optional
  • •isPresent() and isEmpty() check presence
  • •get() retrieves the value but throws if empty. Prefer orElse or ifPresent
  • •orElse(default) returns the value or a default
  • •orElseGet(supplier) returns the value or computes a default lazily
  • •ifPresent(consumer) runs the consumer only if a value is present
  • •map, filter, flatMap work on Optional like on streams

Examples

Creating and using Optional

import java.util.Optional;

public class Main {
    static Optional<String> findUser(int id) {
        if (id == 1) return Optional.of("Alice");
        if (id == 2) return Optional.of("Bob");
        return Optional.empty();
    }

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

        // Safe patterns
        user1.ifPresent(name -> System.out.println("Found: " + name)); // Found: Alice

        String name = missing.orElse("Unknown");
        System.out.println(name); // Unknown

        // Compute default only when needed (lazy)
        String computed = missing.orElseGet(() -> "Default-" + System.currentTimeMillis());
        System.out.println(computed);

        // orElseThrow
        try {
            missing.orElseThrow(() -> new RuntimeException("User not found"));
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
        }
    }
}

orElseGet takes a Supplier (lazy). orElse evaluates the argument eagerly even if the Optional is present. Use orElseGet when the default computation is expensive.

Chaining Optional with map and filter

import java.util.Optional;

public class Main {
    static Optional<String> getUsername(int userId) {
        if (userId == 42) return Optional.of("alice_wonder");
        return Optional.empty();
    }

    public static void main(String[] args) {
        // map transforms the value if present
        Optional<String> upper = getUsername(42)
            .map(String::toUpperCase);
        System.out.println(upper.orElse("no user")); // ALICE_WONDER

        // filter retains the value only if condition holds
        Optional<String> longName = getUsername(42)
            .filter(s -> s.length() > 5);
        System.out.println(longName.isPresent()); // true

        // Chaining: get username, check it is not "admin"
        getUsername(42)
            .filter(name -> !name.equals("admin"))
            .map(name -> "Welcome, " + name + "!")
            .ifPresent(System.out::println); // Welcome, alice_wonder!

        // Empty chain
        getUsername(99)
            .map(String::toUpperCase)
            .ifPresent(System.out::println); // nothing printed
    }
}

map and filter on Optional propagate emptiness. If the Optional is empty, the chain short-circuits and the result is empty.

Optional with streams

import java.util.List;
import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = List.of(3, 7, 2, 9, 4, 6, 1);

        // findFirst returns Optional
        Optional<Integer> firstEven = nums.stream()
            .filter(n -> n % 2 == 0)
            .findFirst();
        firstEven.ifPresent(n -> System.out.println("First even: " + n)); // 2

        // max returns Optional
        nums.stream()
            .max(Integer::compareTo)
            .ifPresent(m -> System.out.println("Max: " + m)); // 9

        // Stream of optionals
        List<Optional<String>> optionals = List.of(
            Optional.of("Alice"), Optional.empty(), Optional.of("Bob")
        );
        optionals.stream()
                 .filter(Optional::isPresent)
                 .map(Optional::get)
                 .forEach(System.out::println); // Alice
Bob
    }
}

Stream terminal operations like findFirst, findAny, min, max, reduce return Optional to force callers to handle the empty case.

Common Mistakes

  • Calling get() on an Optional without checking isPresent(). This throws NoSuchElementException. Use orElse, orElseGet, or ifPresent instead
  • Using Optional as a method parameter: Optional<String> name is an antipattern. Accept String directly and let callers pass null (or better, use overloading)
  • Using Optional.of(null): this throws NullPointerException. Use Optional.ofNullable(value) when the value might be null

Note

The most common Optional idiom in production is: return Optional.ofNullable(result) at the end of a search method, and optional.orElseThrow(ExceptionType::new) at the call site when absence is an error.

Exercises

Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.

Loading editor…

Completed: 0 / 4 exercises

Knowledge Check

Question 1 of 4

What is the safe way to get a value from an Optional when it might be empty?