NullPointerException is the most common runtime error in Java. It happens when code assumes a value exists and it doesn’t: a customer without an address, a lookup that found nothing, a missing field in an API response. Optional<T> (Java 8) makes “there might be no value” part of the type, so callers have to deal with it.
Creating Optionals
Optional<String> some = Optional.of("value"); // must not be null
Optional<String> none = Optional.empty();
Optional<String> maybe = Optional.ofNullable(lookup()); // null becomes empty
The most common use: return an Optional from a method when “not found” is a normal outcome.
static Optional<Customer> findCustomer(String id) {
return Optional.ofNullable(DB.get(id));
}
Now the method’s signature tells everyone: this might not find anything.
Getting the value out — safely
findCustomer("C1").map(Customer::name).orElse("unknown"); // a default
findCustomer("C9").map(Customer::name).orElseGet(() -> "Guest-" + id); // computed default
findCustomer("C9").orElseThrow(() -> new NoSuchElementException("Customer C9 not found"));
findCustomer("C1").ifPresent(c -> System.out.println(c.name()));
findCustomer("C9").ifPresentOrElse(
c -> System.out.println("Found " + c.name()),
() -> System.out.println("No such customer"));
Watch out: orElse(x) always evaluates x, even when the Optional has a value. If the default is expensive (a database call, building a big object), use orElseGet(() -> x) — it only runs when needed.
Transforming: map, flatMap, filter
The real power is chaining through data that might be missing at any step:
String city = findCustomer("C2")
.map(Customer::address) // null address → empty Optional
.map(Address::city)
.orElse("no city on file");
Without Optional, this is a pyramid of if (x != null) checks. With it, any missing link simply produces an empty result.
map(f)— transform the value if present (anullresult becomes empty).flatMap(f)— whenfitself returns anOptional, to avoidOptional<Optional<T>>.filter(p)— keep the value only if it matches.or(() -> other)(Java 9+) — try another source if empty.stream()(Java 9+) — zero or one element, perfect inside stream pipelines:
List<String> emails = ids.stream()
.map(OptionalDemo::findCustomer)
.flatMap(Optional::stream) // drop the empties
.map(Customer::email)
.filter(Objects::nonNull)
.toList();
Best practices
- ✅ Use Optional as a return type for “might not exist” results.
- ✅ Chain with
map/filter/orElseinstead of checkingisPresent()and callingget(). - ❌ Don’t call
get()without checking — it throwsNoSuchElementException, which is just aNullPointerExceptionwith a new name. PreferorElseThrow()when absence is an error. - ❌ Don’t use Optional for fields or method parameters. It adds overhead and awkwardness; use
nullchecks or overloads there. - ❌ Don’t return
nullfrom a method that returns Optional. ReturnOptional.empty(). - ❌ Don’t wrap collections. Return an empty list instead of
Optional<List<T>>.
The full example
// OptionalDemo.java — Optional instead of null.
import java.util.*;
public class OptionalDemo {
record Address(String city) { }
record Customer(String name, Address address, String email) { }
static final Map<String, Customer> DB = Map.of(
"C1", new Customer("Acme", new Address("Delhi"), "ap@acme.test"),
"C2", new Customer("Globex", null, null));
// Return Optional when "no result" is a normal outcome
static Optional<Customer> findCustomer(String id) {
return Optional.ofNullable(DB.get(id));
}
public static void main(String[] args) {
// Creating Optionals
Optional<String> some = Optional.of("value");
Optional<String> none = Optional.empty();
Optional<String> maybe = Optional.ofNullable(null);
System.out.println(some.isPresent() + " " + none.isEmpty() + " " + maybe.isPresent()); // true true false
// Getting a value safely
System.out.println(findCustomer("C1").map(Customer::name).orElse("unknown")); // Acme
System.out.println(findCustomer("C9").map(Customer::name).orElse("unknown")); // unknown
// Chaining through possibly-missing data with map / flatMap
String city = findCustomer("C2")
.map(Customer::address) // null address -> empty Optional
.map(Address::city)
.orElse("no city on file");
System.out.println(city); // no city on file
// filter
findCustomer("C1")
.map(Customer::email)
.filter(e -> e.endsWith(".test"))
.ifPresent(e -> System.out.println("Test email: " + e));
// ifPresentOrElse (Java 9+)
findCustomer("C9").ifPresentOrElse(
c -> System.out.println("Found " + c.name()),
() -> System.out.println("No such customer"));
// orElseGet: compute the fallback only when needed
String name = findCustomer("C9").map(Customer::name).orElseGet(() -> "Guest-" + 42);
System.out.println(name);
// orElseThrow: when absence is really an error
try {
findCustomer("C9").orElseThrow(() -> new NoSuchElementException("Customer C9 not found"));
} catch (NoSuchElementException e) {
System.out.println(e.getMessage());
}
// or (Java 9+): try another source
Optional<Customer> c = findCustomer("C9").or(() -> findCustomer("C1"));
System.out.println(c.map(Customer::name).get()); // Acme
// stream() (Java 9+): turn many Optionals into the present values
List<String> emails = List.of("C1", "C2", "C9").stream()
.map(OptionalDemo::findCustomer)
.flatMap(Optional::stream)
.map(Customer::email)
.filter(Objects::nonNull)
.toList();
System.out.println(emails); // [ap@acme.test]
}
}
Try it yourself
Write Optional<Integer> parseQuantity(String text) that returns an empty Optional when the text isn’t a positive whole number. Then print the quantity doubled, or "invalid", for "12", "-3" and "abc".
Show a solution
static Optional<Integer> parseQuantity(String text) {
try {
int q = Integer.parseInt(text.trim());
return q > 0 ? Optional.of(q) : Optional.empty();
} catch (NumberFormatException e) {
return Optional.empty();
}
}
for (String t : List.of("12", "-3", "abc")) {
System.out.println(parseQuantity(t).map(q -> String.valueOf(q * 2)).orElse("invalid"));
}
// 24, invalid, invalid
Key takeaways
- Optional makes “maybe no value” explicit in the type.
- Create with
of,ofNullable,empty; return it from lookups. - Chain with
map,flatMap,filter; finish withorElse,orElseGet,orElseThroworifPresent. - Avoid
get(), Optional fields and Optional parameters.