Definition
- orElse(T): always evaluates the argument, even if the Optional is present. The result is discarded when present.
- orElseGet(Supplier
) : invokes the Supplier only when the Optional is empty. Zero computation when present. - Cheap default: orElse("N/A") or orElse(BigDecimal.ZERO) is fine (trivial cost).
- Expensive default: orElseGet(() -> repository.findById(...)) skips the query when present.
- orElseThrow: also lazy. The exception Supplier is invoked only when empty.
orElse is like the bank pre-printing a default cheque every time, then checking if Rajesh brought his own. If yes, the pre-printed cheque is thrown away: wasted ink. orElseGet checks first: did Rajesh bring a cheque? If no, then print a default. If yes, do not bother printing. Same result, but orElseGet saves resources.
Understanding the Concepts
How do orElse and orElseGet differ?
package com.bank.service;
import java.util.Optional;
public class OrElseVsOrElseGet {
static String expensiveDefault() {
System.out.println("Computing expensive default...");
return "DEFAULT_ACCOUNT";
}
public static void main(String[] args) {
Optional<String> present = Optional.of("SAV2001");
// orElse: expensiveDefault() CALLED even when present!
String r1 = present.orElse(expensiveDefault());
System.out.println("orElse result: " + r1);
System.out.println("---");
// orElseGet: expensiveDefault() NOT called when present!
String r2 = present.orElseGet(OrElseVsOrElseGet::expensiveDefault);
System.out.println("orElseGet result: " + r2);
}
}
Computing expensive default...
orElse result: SAV2001
---
orElseGet result: SAV2001
orElse called the expensive method even though the Optional had a value. orElseGet did not.
When does the difference matter?
| Default cost | orElse | orElseGet | Recommendation |
|-------------|--------|-----------|----------------|
| Cheap constant | Trivial waste | Same result | Either is fine |
| Database query | Wasted query | No query | orElseGet |
| HTTP call | Wasted network | No call | orElseGet |
| Object creation | Wasted allocation | No allocation | orElseGet |
Why is orElse eager?
orElse accepts a value, not a Supplier. Java evaluates method arguments before passing them. So orElse(expensiveMethod()) calls expensiveMethod() first, then passes the result to orElse.
// orElse(T defaultValue) – T is already computed
String r = opt.orElse("literal"); // cheap, fine
String r = opt.orElse(repository.findById("X")); // DB query runs regardless!
// orElseGet(Supplier<T>) – Supplier defers computation
String r = opt.orElseGet(() -> repository.findById("X")); // query only if empty
What about orElseThrow?
orElseThrow follows the same lazy pattern: the exception Supplier is invoked only when the Optional is empty.
import java.math.BigDecimal;
BigDecimal balance = findBalance("SAV2001")
.orElseThrow(() -> new AccountNotFoundException("SAV2001"));
// Exception created ONLY if empty. No waste.
// Java 10+: no-arg orElseThrow throws NoSuchElementException
BigDecimal balance = findBalance("SAV2001").orElseThrow();
What is a real production scenario?
package com.bank.service;
import java.util.Optional;
public class AccountService {
private final AccountRepository repository;
private final AccountCache cache;
public Account findAccount(String id) {
// WRONG: orElse queries DB even on cache hit
// return cache.get(id).orElse(repository.findById(id));
// CORRECT: orElseGet queries DB only on cache miss
return cache.get(id).orElseGet(() -> repository.findById(id));
// With 80% cache hit rate: eliminates 80% of DB queries
}
}
What are common mistakes?
// WRONG: using orElse with side effects
Account a = findAccount(id).orElse(createDefaultAccount());
// createDefaultAccount() runs AND may insert into DB even when account exists!
// CORRECT: orElseGet defers the side effect
Account a = findAccount(id).orElseGet(() -> createDefaultAccount());
// Default created ONLY when account not found
// WRONG: orElse with expensive computation in a loop
for (String id : accountIds) {
String name = cache.get(id).orElse(repository.findName(id)); // N DB queries
}
// CORRECT: orElseGet
for (String id : accountIds) {
String name = cache.get(id).orElseGet(() -> repository.findName(id)); // only on miss
}
What about or() (Java 9)?
// or() returns an Optional, enabling chained fallbacks
Optional<Account> account = findInCache(id)
.or(() -> findInLocalDB(id))
.or(() -> findInRemoteService(id));
// Tries each source lazily until one returns a present Optional
What should a senior engineer know?
- The rule: if the default involves any computation, I/O, or side effect, always use orElseGet.
- Under production load with 80% cache hit rate, orElseGet eliminates hundreds of wasted queries per second.
- In code reviews, flag any
orElse(repository.find(...))as a performance bug. - or() (Java 9) chains Optional sources lazily, useful for cache-then-DB-then-remote patterns.
Visual Overview
orElse(expensive()): orElseGet(() -> expensive()): ┌────────────────────┐ ┌────────────────────┐ │ 1. Call expensive() │ │ 1. Check: present? │ │ 2. Check: present? │ │ YES → return val│ │ YES → discard! │ waste │ NO → call fn() │ no waste │ NO → use default │ │ │ └────────────────────┘ └────────────────────┘
Key Takeaway
orElse evaluates its default always, even when present. orElseGet evaluates only when empty. For cheap constants, both are fine. For expensive operations like database queries, orElseGet prevents unnecessary computation.
The rule: if the default involves any computation, I/O, or side effect, always use orElseGet.
Remember one sentence: orElse is eager (always evaluates), orElseGet is lazy (only when empty), so use orElseGet for expensive defaults.
---