Optional orElseGet Patterns
`Optional` makes the absence of a value explicit, but the `orElse` vs `orElseGet` choice trips people up. This snippet contrasts the two, shows `orElseThrow` for required-value contracts, and demonstrates `map`/`flatMap` chaining for null-safe field access. Reach for `orElseGet` whenever the default is expensive to compute.
1,068 views
13
import java.util.Optional;
public class Main {
static String expensiveDefault() {
System.out.println(" computing default...");
return "FALLBACK";
}
public static void main(String[] args) {
Optional<String> present = Optional.of("value");
System.out.println("orElse on present:");
String a = present.orElse(expensiveDefault()); // ALWAYS evaluates the arg
System.out.println(" -> " + a);
System.out.println("orElseGet on present:");
String b = present.orElseGet(Main::expensiveDefault); // ONLY runs if empty
System.out.println(" -> " + b);
System.out.println("orElseGet on empty:");
String c = Optional.<String>empty().orElseGet(Main::expensiveDefault);
System.out.println(" -> " + c);
}
}orElse(value) evaluates its argument unconditionally, even when the Optional is non-empty. That is fine for a literal or cached value but wasteful when the fallback requires a network call or heavy computation. orElseGet(Supplier) is lazy: it only invokes the supplier when the Optional is actually empty. Always prefer orElseGet when the default has any cost, and reserve orElse for cheap constants like "" or 0.
import java.util.Optional;
public class Main {
static Optional<String> findUserEmail(int id) {
return id == 1 ? Optional.of("[email protected]") : Optional.empty();
}
public static void main(String[] args) {
// Java 8 form: orElseThrow with a supplier
String email = findUserEmail(1).orElseThrow(
() -> new IllegalStateException("user 1 must have email")
);
System.out.println("hit: " + email);
try {
findUserEmail(99).orElseThrow(() -> new IllegalStateException("user 99 missing"));
} catch (IllegalStateException e) {
System.out.println("miss: " + e.getMessage());
}
}
}orElseThrow is for the happy-path contract: "this Optional must be present, otherwise the program is in an invalid state". The supplier form lets you craft a meaningful exception with context. Java 10 added a no-arg orElseThrow() that throws NoSuchElementException with a generic message, but the supplier form is almost always preferable because it carries the failing key or id. Use this in service layers where missing data signals a bug rather than a normal branch.
import java.util.Optional;
public class Main {
static class User {
Optional<Address> address;
User(Address a) { this.address = Optional.ofNullable(a); }
}
static class Address {
String city;
Address(String city) { this.city = city; }
}
static Optional<User> findUser(int id) {
if (id == 1) return Optional.of(new User(new Address("Berlin")));
if (id == 2) return Optional.of(new User(null));
return Optional.empty();
}
public static void main(String[] args) {
for (int id : new int[]{1, 2, 3}) {
String city = findUser(id)
.flatMap(u -> u.address)
.map(a -> a.city)
.map(String::toUpperCase)
.orElse("unknown");
System.out.println(id + " -> " + city);
}
}
}map transforms the value when present and skips when empty; flatMap is the same but for transformations that already return an Optional (so you do not end up with Optional<Optional<X>>). Chaining these is the null-safe equivalent of ?. in Kotlin or ?. in JavaScript. The whole pipeline collapses to "unknown" cleanly whenever any link is empty: no NPE, no nested null checks, no early returns. This is the pattern that justifies introducing Optional to a codebase in the first place.
