What problem Optional solves (and does not), orElse vs orElseGet performance, orElseThrow behaviour, Optional.map vs flatMap and chaining optionals, Optional best practices and anti-patterns, default-method diamond conflicts, why default methods cannot override Object methods, static interface methods vs class static methods, and why default methods cannot be synchronized or abstract.
Published September 25, 2026
Optional is heavily misused, so interviewers ask for best practices, not the API. For default methods, they probe the resolution rules and the language restrictions. Know the why behind each rule.
Optional solve in Java 8?Short answer: It makes "there may be no result" explicit in a method's return type, so callers can't forget the absent case. Otherwise, null returns lead to NullPointerExceptions far from their cause. It also provides a fluent API for handling absence (map, filter, orElse, ifPresentOrElse), instead of nested null checks.
What it does not do: it doesn't eliminate null from Java, it isn't a general-purpose "maybe" field type, and it isn't serialisable. Its designers intended it primarily as a return type.
Learn it in depth → Optional
orElse() differ from orElseGet(), in performance and behaviour?Short answer:
orElse(value): the argument is always evaluated, even when the Optional has a value, because Java evaluates method arguments eagerly.orElseGet(supplier): the supplier runs only if the Optional is empty.So orElse(loadDefaultFromDatabase()) hits the database every time, which is a hidden performance bug and a side-effect bug. Use orElse for constants or already-computed values, and orElseGet for anything expensive or with side effects.
Customer c1 = repo.findById(id).orElse(createGuestCustomer()); // creates a guest on EVERY call
Customer c2 = repo.findById(id).orElseGet(this::createGuestCustomer); // only when absent
orElseThrow() on an empty Optional?Short answer:
orElseThrow() (no-arg, Java 10) throws NoSuchElementException("No value present"). It's the preferred replacement for get(): same behaviour, but the name makes the intent explicit.orElseThrow(Supplier<? extends X>) throws your exception, created lazily. For example orElseThrow(() -> new OrderNotFoundException(id)), mapped to a 404 by an exception handler.On a present Optional, both simply return the value.
Optional.map() and Optional.flatMap()? How do you chain several optionals with flatMap()?Short answer:
map(fn): fn returns a plain value, which is wrapped automatically: Optional<T> → Optional<R>. If fn returns null, the result is empty.flatMap(fn): fn already returns an Optional<R>, and it isn't wrapped again. It avoids Optional<Optional<R>>.Use flatMap to chain lookups that can each be absent:
Optional<String> city = userRepo.findById(userId) // Optional<User>
.flatMap(User::primaryAddress) // User -> Optional<Address>
.map(Address::city) // Address -> String
.filter(c -> !c.isBlank());
// Java 9: or() supplies an alternative Optional; stream() turns it into a 0-or-1 element stream
Optional<Price> price = cache.get(sku).or(() -> pricingService.find(sku));
List<Address> addresses = users.stream().map(User::primaryAddress).flatMap(Optional::stream).toList();
Optional?Short answer:
findById, findFirst);map/flatMap/filter;orElse (constants), orElseGet (computed values) or orElseThrow (a domain exception);ifPresentOrElse, or and stream (Java 9).get() without a check (use orElseThrow());if (opt.isPresent()) { opt.get() }, which is just a verbose null check;Optional for fields (not serialisable, extra allocation; use null internally, with an Optional-returning getter);@Nullable);Optional<List<>> (return an empty collection instead);null from an Optional-returning method, which defeats the point;Optional.of(x) when x might be null (use ofNullable).OptionalInt/OptionalLong/OptionalDouble for primitives.Optional. Jackson needs the jdk8 module to serialise it (DTOs shouldn't contain Optional fields anyway).Short answer: It's a compile error ("class inherits unrelated defaults"), unless the class overrides the method. Inside the override, it can call a specific parent with InterfaceName.super.method():
interface Flyer { default String move() { return "fly"; } }
interface Swimmer { default String move() { return "swim"; } }
class Duck implements Flyer, Swimmer {
@Override public String move() { return Flyer.super.move() + " and " + Swimmer.super.move(); }
}
The resolution rules:
B extends A, and both define the default, then B's version is chosen.Object?Short answer: No, it's a compile error. An interface can't declare a default equals, hashCode or toString. The reason is rule 1: classes win. Every class inherits Object's implementations, so such a default could never be selected, which would be confusing and useless. Interfaces may redeclare them as abstract (for documentation, like Comparator.equals), and that doesn't affect functional-interface status.
Short answer:
Comparator.naturalOrder(), List.of()), never through an implementation or an instance. They're implicitly public, and can be private since Java 9.The design use: static factories and helpers tied to the interface's concept (Stream.of, Map.entry, Predicate.not), without companion utility classes like Collections.
synchronized or abstract? Why not?Short answer:
abstract: no. A default method has a body by definition. An abstract interface method is just a normal method without default.
synchronized: not allowed (it's a compile error). The reasoning:
synchronized default would lock on this, silently imposing a locking policy on every implementing class, which could deadlock or conflict with its own locking;synchronized.If you need locking inside a default method, use a synchronized block on an explicit lock, carefully, or leave synchronisation to the implementations.
final is also disallowed on defaults: they must remain overridable.
Optional's role compared with a plain null check, in one line? (Rapid-fire)Short answer: Optional is a return-type contract that forces callers to handle absence, through a composable API. Plain null has no type-level signal. Internally (fields, local hot paths), null plus nullness annotations (JSpecify) is often simpler.
Q: Why isn't Optional Serializable?
A: It was designed as a return-type idiom, not a field type. Making it serialisable would encourage storing it in fields and DTOs, and constrain its future evolution (for example, toward a value class).
Q: What does Optional.empty().map(x -> ...) do?
A: Nothing. The function isn't called, and the result is another empty Optional. That's why chains short-circuit safely.
Q: How do you convert an Optional into a stream of zero or one elements?
A: optional.stream() (Java 9). It's useful for flatMap-ing a list of lookups into only the present results.
Q: Why were private interface methods added in Java 9? A: So several default or static methods can share helper code, without exposing it as part of the interface's public API.