Back to Optional
Q3Question 3 of 9

orElse() vs orElseGet()?

Key Concepts
  • orElse(defaultValue) ALWAYS evaluates the default, even when the Optional is present.
  • orElseGet(Supplier) evaluates the default ONLY when the Optional is empty (lazy).
  • For cheap defaults (string literal, BigDecimal.ZERO), both are equivalent.
  • For expensive defaults (database query, HTTP call), orElseGet prevents unnecessary computation.
  • orElseThrow follows the same lazy pattern: the exception Supplier is invoked only when empty.
  • A common code review mistake: orElse(repository.findById(...)) executes the DB query even when present.
  • Under production load with 80% cache hit rate, orElseGet eliminates hundreds of wasted queries per second.

Definition

orElse always evaluates the default (eager); orElseGet evaluates only when empty (lazy): use orElseGet for expensive defaults like database calls.
  • 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?

JAVA
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);
    }
}
TEXT
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.

JAVA
// 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.

JAVA
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?

JAVA
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?

JAVA
// 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)?

JAVA
// 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

DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate