The difference between orElse() and orElseGet() is when the default value is computed. The orElse() method takes a ready value, so Java evaluates its argument every time, while orElseGet() takes a Supplier and calls it only when the Optional is empty. Both methods return the value inside the Optional when one is present.
We use orElse() for a constant or a value we already hold, such as a default theme name. We use orElseGet() when building the default costs time or changes something, such as reading a file, calling a service or inserting a database row.
The following example reads a saved theme with both methods. The ThemeStore class counts how often its loadDefault() method runs.
Optional<String> saved = Optional.of("dark");
ThemeStore store = new ThemeStore();
String eager = saved.orElse(store.loadDefault()); // "dark"
String lazy = saved.orElseGet(store::loadDefault); // "dark"
int lookups = store.lookups(); // 1, from the orElse() line
Notice that both calls return “dark”, yet loadDefault() ran once, for the orElse() line, even though a value was present. We look at the method parameters, the evaluation order, a real bug caused by an eager default, the performance cost, and the related methods orElseThrow() and or().
1. The Difference between orElse() and orElseGet() Methods
Both methods answer the same question, which value do we use when the Optional is empty. They differ in what they accept and therefore in when the default is built.

| Feature | orElse(T other) | orElseGet(Supplier<? extends T> supplier) |
|---|---|---|
| Parameter | A value of type T, which may be null | A Supplier that produces the value |
| When the default is computed | Always, before orElse() is called (eager) | Only when the Optional is empty (lazy) |
| Side effects in the default | Run on every call | Run only for an empty Optional |
| Good fit | Constants, literals, fields, values already computed | Method calls, I/O, object creation, logging, database access |
| Typical line | theme.orElse(“light”) | theme.orElseGet(this::loadDefault) |
1.1. Method Parameters
The orElse() method takes the default value itself. The orElseGet() method takes a Supplier, a functional interface with one method get() that returns the value, so we pass a lambda expression or a method reference.
A small ThemeStore class makes the difference visible. Its loadDefault() method stands in for any expensive lookup, and the lookups counter shows whether the lookup ran.
class ThemeStore {
private int lookups;
String loadDefault() {
lookups++; // stands in for a file read or a remote call
return "light";
}
int lookups() {
return lookups;
}
}
1.2. Evaluation of Default Value
The default value provided to orElse() is eagerly evaluated. Java evaluates every method argument before it calls the method, so store.loadDefault() runs first and its result is passed to orElse(). The orElse() method throws that result away when the Optional holds a value, but the work is already done.
Optional<String> saved = Optional.of("dark");
ThemeStore store = new ThemeStore();
String theme = saved.orElse(store.loadDefault()); // "dark"
int lookups = store.lookups(); // 1, loadDefault() ran anyway
The supplier passed to orElseGet() is lazily evaluated. The method reference store::loadDefault creates a Supplier object, and orElseGet() calls its get() method only when no value is present.
Optional<String> saved = Optional.of("dark");
Optional<String> notSaved = Optional.empty();
ThemeStore store = new ThemeStore();
String theme = saved.orElseGet(store::loadDefault); // "dark"
int before = store.lookups(); // 0, the supplier was not called
String fallback = notSaved.orElseGet(store::loadDefault); // "light"
int after = store.lookups(); // 1
For an empty Optional, the two methods behave the same way. Both run the lookup once and return “light”, so the difference shows up only when a value is present.
2. When an Eager Default Causes a Bug
The extra call made by orElse() is more than wasted time when the default has a side effect. Any code that writes, sends or counts something runs on every call, including the calls where the Optional already has a value.
Say a shop has a get-or-create step at checkout. It looks up the customer by email, and creates a customer row only for a first-time buyer. The CustomerRepository class keeps rows in a list, so a duplicate insert stays visible.
class CustomerRepository {
private final List<String> rows = new ArrayList<>();
Optional<String> findByEmail(String email) {
return rows.stream().filter(email::equals).findFirst();
}
String insert(String email) {
rows.add(email);
return email;
}
int rowCount() {
return rows.size();
}
}
With orElse(), the insert runs before the lookup result is checked. A returning customer gets a second row, and in a real database the same line either creates a duplicate or throws a unique-constraint error.
CustomerRepository repo = new CustomerRepository();
repo.insert("ana@mail.com");
String customer = repo.findByEmail("ana@mail.com")
.orElse(repo.insert("ana@mail.com"));
int rows = repo.rowCount(); // 2, a duplicate row
Switching to orElseGet() fixes the bug, because the insert moves inside the supplier and runs only for an unknown email.
CustomerRepository repo = new CustomerRepository();
repo.insert("ana@mail.com");
String known = repo.findByEmail("ana@mail.com")
.orElseGet(() -> repo.insert("ana@mail.com"));
int rowsAfterKnown = repo.rowCount(); // 1
String fresh = repo.findByEmail("raj@mail.com")
.orElseGet(() -> repo.insert("raj@mail.com"));
int rowsAfterNew = repo.rowCount(); // 2, one row per customer
The same rule applies to defaults that log a warning, increment a metric, send an event or open a connection. In code reviews, we read the argument of orElse() as code that always runs, which makes a call such as orElse(createSomething()) stand out.
3. Performance Impact
When the default is a literal, a constant or a variable, orElse() costs nothing extra, because the value already exists. When the default is a method call, orElse() pays for that call every time, and the cost grows with the method, such as a file read, a remote call or a large object allocation.
The orElseGet() method has a small cost of its own. The JVM creates a Supplier object for the lambda, although a lambda that captures no variables is created once and reused. A lambda that captures local variables, such as () -> repo.insert(email), may need a new small object each time the line runs.
Benchmarks that show orElseGet() thousands of times faster measure the fallback method, not Optional. For a constant default, the choice between the two methods makes no measurable difference, so readability decides.
4. When to Use orElse() or orElseGet()?
We use orElse() when the default value already exists or is a cheap constant. We use orElseGet() when building the default calls a method, creates an object or has a side effect.
| Default value | Method | Example |
|---|---|---|
| String or number literal | orElse() | orElse(“light”) |
| Constant or field | orElse() | orElse(DEFAULT_THEME) |
| null for legacy APIs | orElse() | orElse(null) |
| Method call or calculation | orElseGet() | orElseGet(this::loadDefault) |
| New object | orElseGet() | orElseGet(ArrayList::new) |
| Database write, remote call, logging | orElseGet() | orElseGet(() -> repo.insert(email)) |
| Missing value is an error | orElseThrow() | orElseThrow(() -> new NotFoundException(id)) |
If the argument of orElse() contains parentheses, we check whether it calls a method, and if it does, we switch to orElseGet(). An object creation such as new ArrayList<>() counts too, because it runs a constructor.
5. Related Methods orElseThrow() and or()
Two more methods handle an empty Optional, and they cover cases where neither default method fits. The orElseThrow() method fails fast when a missing value is a bug, and or() tries another source that also returns an Optional.
- The no-argument orElseThrow() (Java 10) throws NoSuchElementException and replaces get().
- The orElseThrow(Supplier) form throws the exception we create, and like orElseGet() it builds the exception only for an empty Optional.
- The or(Supplier) method (Java 9) returns another Optional, so we can chain fallbacks, such as a team setting when the user has no setting of their own.
Optional<String> userTheme = Optional.empty();
Optional<String> teamTheme = Optional.of("system");
String theme = userTheme.or(() -> teamTheme).orElse("light"); // "system"
String required = userTheme.orElseThrow(); // NoSuchElementException: No value present
String explained = userTheme.orElseThrow(() -> new IllegalStateException("No theme")); // IllegalStateException: No theme
The Java Optional guide covers these methods together with map(), filter(), stream() and the places where Optional does not belong.
6. orElse() vs orElseGet() FAQs
Eager evaluation surprises developers in two places, null defaults and code that looks harmless inside the argument.
6.1. Is orElseGet() Always Better than orElse()?
No. For a literal, a constant or a variable we already hold, orElse() is shorter and does no extra work. The orElseGet() method helps only when building the default runs code, so wrapping a constant in a lambda, as in orElseGet(() -> “light”), adds noise without a benefit.
6.2. Does orElse() Call the Method When the Optional Has a Value?
Yes. Java evaluates the argument before calling orElse(), so a method call inside the parentheses runs even when the value is present. The orElse() method ignores the result, but any side effect of the call has already happened, as the duplicate row in section 2 shows.
6.3. Can orElse() or orElseGet() Return null?
Yes. The orElse() method accepts null as the default, and orElseGet() returns whatever the supplier returns, including null. A null supplier is different, because orElseGet() throws NullPointerException when it needs to call it.
Optional<String> none = Optional.empty();
Supplier<String> missing = null;
String fromOrElse = none.orElse(null); // null
String fromSupplier = none.orElseGet(() -> null); // null
String present = Optional.of("dark").orElseGet(missing); // "dark", supplier not needed
String broken = none.orElseGet(missing); // NullPointerException
6.4. When Do We Use or() Instead of orElseGet()?
The orElseGet() method returns a plain value of type T and ends the chain. The or() method returns an Optional<T>, so the fallback may also be empty and we can add more fallbacks or call map() afterwards. Both take a Supplier and call it only for an empty Optional.
6.5. Should I Use orElseThrow() Instead of orElseGet()?
Yes, when a missing value means the request cannot continue, for example an order ID that must exist. Returning a made-up default in that case hides the problem, while orElseThrow() stops with a clear exception that a REST controller can turn into a 404 response.
7. Conclusion
Both orElse() and orElseGet() return the value of a present Optional and a default for an empty one. The orElse() method takes the default as a value, so its argument is always computed, while orElseGet() takes a Supplier and computes the default only when it is needed.
For literals and constants, orElse() is the clearer choice. For method calls, new objects and anything with side effects, such as a database insert, we use orElseGet(), and when a missing value is an error, orElseThrow().
8. References
Happy Learning !!