skip to content

What is the difference between orElse and orElseGet, and why can it matter for correctness, not just performance?

level: seniorimportance: should knowfreq 55%

answer

  1. orElse → fallback evaluated eagerly, always
  2. orElseGet → supplier evaluated lazily, only when empty
  3. Args in Java are evaluated before the call (eager)
  4. Expensive/side-effecting default → use orElseGet
  5. Side effect in orElse runs even when value present (a bug)

basics

~20 s

orElse(x) always builds x even when the Optional has a value; orElseGet(supplier) only runs the supplier when the Optional is empty. So orElseGet avoids wasted work, and matters when the default is expensive or has side effects.

solid answer

~50 s

Both return the contained value when present and a fallback when empty. The difference is evaluation timing of the fallback. orElse(other) takes an already-computed value, so its argument is evaluated eagerly every time the line runs — even when the Optional is non-empty and the fallback is thrown away. orElseGet(Supplier) takes a lambda and invokes it lazily, only when the Optional is empty. For a cheap constant the choice is cosmetic, but it becomes a correctness issue when the fallback does real work: a database call, object allocation in a hot loop, or anything with side effects. Writing optional.orElse(expensiveLookup()) calls expensiveLookup() unconditionally, which can be slow or even wrong (e.g., it increments a counter or inserts a row) on the path where the Optional already had a value. The rule: orElse for plain constants or already-available values, orElseGet whenever computing the default is costly or has side effects.

code

java · 12 lines
java
Optional<String> present = Optional.of("value");

// orElse: makeDefault() ALWAYS runs, result discarded here
String a = present.orElse(makeDefault());

// orElseGet: supplier NOT called because value is present
String b = present.orElseGet(() -> makeDefault());

static String makeDefault() {
    System.out.println("expensive/side-effecting work"); // runs for orElse, not orElseGet
    return "default";
}

go deeper

for a junior

Know that orElse provides a default value and orElseGet provides a default via a lambda.

for a middle

Explain that orElse evaluates its argument eagerly while orElseGet runs the supplier only when empty, and prefer orElseGet for expensive defaults.

for a senior

Articulate that eager evaluation of orElse makes side-effecting defaults a correctness bug (not just slow), and choose between orElse/orElseGet by cost and side effects.

for a principal

Generalize the eager-vs-lazy argument-evaluation principle, set conventions/static-analysis to catch orElse with method-call arguments, and reason about default-computation cost across hot paths and APIs.

## The two methods Both `orElse` and `orElseGet` answer the same question — "give me the value, or a fallback if empty" — but they take the fallback in different forms: - **`T orElse(T other)`** — you pass an actual value. - **`T orElseGet(Supplier<? extends T> supplier)`** — you pass a `Supplier`, a no-argument lambda that *produces* the value when called. ## The key difference: when the fallback is evaluated In Java, **method arguments are evaluated before the method runs** (eager evaluation). So with `orElse`, whatever expression you write as the argument is computed *first*, regardless of whether the Optional is empty: ```java Optional<String> opt = Optional.of("present"); String r = opt.orElse(makeDefault()); // makeDefault() RUNS, even though "present" wins ``` Here `makeDefault()` executes and its result is then discarded, because the Optional already had a value. The work was wasted. With `orElseGet`, you hand over a lambda that is **only invoked when the Optional is empty**: ```java String r = opt.orElseGet(() -> makeDefault()); // makeDefault() does NOT run; value is "present" ``` The supplier is a deferred computation; `orElseGet` calls it lazily, on the empty branch only. ## Why this is more than a micro-optimization If the fallback were a pure, cheap constant like `""` or `0`, the difference is just a negligible bit of wasted computation, and `orElse` reads more simply. But two situations turn it into a real defect: 1. **Expensive defaults.** `orElse(repository.findDefault())` hits the database on *every* call, including the (possibly common) case where the Optional is present. In a loop or hot path this is a serious performance bug. `orElseGet(() -> repository.findDefault())` only queries when needed. 2. **Side-effecting defaults.** If the fallback does something observable — logs, increments a metric, creates and registers an object, mutates shared state — then `orElse` performs that side effect *unconditionally*, even when it shouldn't. For example, `orElse(createNewSession())` would create a session every time, leaking objects or double-counting, whereas `orElseGet(() -> createNewSession())` creates one only when truly absent. This is a correctness bug, not just slowness. ## A subtle case: returning null `orElse(null)` is legal and sometimes used to unwrap back to a nullable value at an API boundary. `orElseGet(() -> null)` would do the same lazily. Generally, if you find yourself writing `orElse(null)`, reconsider whether Optional is buying you anything at that point. ## Relationship to other unwrap methods `orElse`/`orElseGet` substitute a value; `orElseThrow` instead raises an exception when empty. They're siblings — choose `orElseGet` over `orElse` when the default is costly, `orElseThrow` when absence is an error rather than a recoverable default. ## Summary - `orElse(x)`: x is evaluated **eagerly, always**. - `orElseGet(() -> x)`: the supplier runs **lazily, only when empty**. - Prefer `orElseGet` whenever the default is expensive or has side effects — there it's a correctness concern, not merely performance. Use `orElse` for plain constants.

  • If the default is a simple string constant like \"\", does it matter whether you use orElse or orElseGet?
    Functionally no — both return the same result. orElse is preferable there for simplicity since evaluating a constant is free and has no side effects. The distinction only matters when the default is expensive or side-effecting.
  • Give a concrete example where orElse causes a bug, not just slowness.
    optional.orElse(createAndRegisterSession()) creates and registers a session on every call, including when the Optional already holds one, leaking sessions or corrupting state. orElseGet(() -> createAndRegisterSession()) only does so when empty.

orElse is like cooking a backup meal every night just in case the takeout fails — even when it arrives. orElseGet is keeping the recipe on hand and only cooking if the takeout actually doesn't show up.

saying these in an interview costs you the question

  • Saying orElseGet's supplier always runs (it runs only when empty)
  • Claiming the difference is purely performance and never correctness
  • Thinking orElse evaluates its argument lazily
  • Using orElse(expensiveOrSideEffectingCall()) in hot paths

context