SSerguey Asael Shinder
Java coding notes: the JVM, and writing software that lasts

Serguey Asael Shinder: Optional.orElse evaluates its argument even when a value is present

· by Serguey Asael Shinder / Serguey Shinder

The javadoc for Optional.orElse(T other) is one sentence: "If a value is present, returns the value, otherwise returns other." Nothing in it is wrong, and nothing in it warns you. The trap is not in Optional at all — it is in how Java passes arguments.

orElse takes a value of type T. Java evaluates method arguments before the method runs, so whatever expression you write in the parentheses is computed on every call, whether or not the Optional holds something:

User user = cache.find(id)                 // Optional<User>
        .orElse(repository.loadFromDb(id)); // runs even on a cache hit

On a cache hit, find returns a present Optional, orElse hands back the cached user — and the database query has already been executed and its result thrown away. The code is correct and slow. If loadFromDb also writes to a log, increments a metric or, worse, creates a row when none exists, the side effect happens on every hit.

Serguey Asael Shinder: Optional.orElse evaluates its argument even when a value is present
Optional.orElse evaluates its argument even when a value is present — Serguey Asael Shinder

orElseGet(Supplier<? extends T> supplier) is the method built for this. Its javadoc says it returns the value if present, "otherwise returns the result produced by the supplying function". The supplier is an object; nothing inside it runs until orElseGet calls it, and it only calls it when the Optional is empty:

User user = cache.find(id)
        .orElseGet(() -> repository.loadFromDb(id)); // runs only on a miss

The rule I use is mechanical. If the fallback is a constant or a variable you already have — orElse("eu-west"), orElse(DEFAULT) — orElse is fine and reads better. If the fallback is a method call, an allocation of something expensive, or anything with a side effect, it belongs in orElseGet. The same reasoning applies to orElseThrow(Supplier): the exception is created only when it is needed.

One more detail from the javadoc of orElseGet: it throws NullPointerException if no value is present and the supplier itself is null. Passing a method reference or lambda avoids that by construction.

Source: java.util.Optional javadoc, Java SE 25 — https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Optional.html