Last 30 Days
No notifications
Optional is Java's answer to NullPointerException — a wrapper that *forces* you to think about the absent case. It pairs perfectly with the modern Java toolbox: var, records, sealed types, switch expressions, pattern matching. This topic ties them all together so your code reads cleanly and never blows up on a stray null.
# Optional & Modern Java
null says nothing about whether absence is *expected*. Optional says it loud and clear at the API boundary.Optional<User> findById(long id); // signature documents "may be missing"Optional.empty(); // no value
Optional.of(value); // throws NPE if value is null
Optional.ofNullable(maybeNull); // safe wrapper for legacy null APIsget() blindlyopt.isPresent(); // boolean
opt.ifPresent(u -> log.info(u.name()));
opt.orElse(defaultUser); // eager default
opt.orElseGet(() -> buildDefault()); // lazy default — preferred when default is expensive
opt.orElseThrow(); // throws NoSuchElementException
opt.orElseThrow(() -> new NotFoundException("user"));
get() exists but is essentially deprecated — it's the same as orElseThrow() and gives no clue you handled absence.Optional<String> name = findUser(id).map(User::name);
Optional<String> upper = name.map(String::toUpperCase);
Optional<Address> addr = findUser(id).flatMap(User::primaryAddress);
Optional<User> active = findUser(id).filter(User::isActive);
map / flatMap / filter chain just like Stream — no null checks needed.Optional>
— return an empty list instead.Optional as a field or constructor parameter — only intended as a return type.opt.get() without a guard — ticking time bomb.if (opt.isPresent()) opt.get()… — use ifPresent / map / orElse instead.public sealed interface Shape permits Circle, Square, Triangle {}
public record Circle(double r) implements Shape {}
public record Square(double side) implements Shape {}
public record Triangle(double a, double b) implements Shape {}double area = switch (shape) {
case Circle c -> Math.PI * c.r() * c.r();
case Square s -> s.side() * s.side();
case Triangle t -> 0.5 * t.a() * t.b();
};
Compiler verifies the switch is *exhaustive* over the sealed hierarchy — add a new permitted type and every switch will fail to compile until updated. Beautiful.var users = new ArrayList<User>(); // type inferred as ArrayList<User>
var line = br.readLine(); // String
Rules: only on local variables with an initialiser. Don't use for nondescript types — var x = service.fetch(); hides too much.String json = """
{
"name": "alice",
"age": 21
}
""";public record User(long id, String name, Optional<String> email) {}User u = new User(1, "alice", Optional.of("a@x.com"));
String mail = u.email().orElse("(none)");