orElse() and orElseGet()

Unwrap an Optional with a fallback, and use orElseGet when the fallback is expensive, because orElse evaluates its argument every time.

Signature
public T orElse(T other)
public T orElseGet(Supplier<? extends T> supplier)
  • Added in Java Java 8

Parameters

ParameterTypeDescription
$otherTorElse only: the value to return if no value is present. May be null.
$supplierSupplier<? extends T>orElseGet only: called only when no value is present, to produce the value to return.

Return value

TThe value if present. Otherwise orElse() returns other and orElseGet() returns the result of calling supplier.

Errors

orElse never throws. orElseGet throws NullPointerException if no value is present and supplier is null.

Both methods return the value inside the Optional, or a fallback when it is empty. The difference is when the fallback is worked out.

orElse evaluates its argument every time

orElse(other) is a normal method call, so Java evaluates other before the call happens, whether or not it will be used. For a constant such as "guest" or 20 that costs nothing. For orElse(loadDefaultsFromDb()) or orElse(createCart(id)) it means a database query or a new row on every request, including the ones where the value was there all along.

orElseGet(supplier) takes a Supplier and only calls it when the Optional is empty. Use it whenever the fallback does real work, allocates something large, or has a side effect.

String theme = savedTheme.orElse("light");                    // cheap constant
Cart cart    = findCart(id).orElseGet(() -> createCart(id));   // only created when missing

Mistakes people make

  • Writing orElse(service.call()) and wondering why the service is hit on every request. This is the classic one, and it can also create duplicate records when the fallback inserts.
  • Swinging to the other extreme and using orElseGet(() -> "guest") for constants. It works, but adds noise; orElse is fine for literals and existing variables.
  • Forgetting that orElse(null) is allowed. It is the right way to hand a value to an older API that expects null for "none", such as a nullable JDBC parameter.

Back-end angle

Defaults are everywhere in services: page size when the query string has none, a locale when the header is missing, a configuration value when the environment variable is unset. Chaining Optional.ofNullable(query.get("size")).map(Integer::parseInt).orElse(20) keeps the parsing and the default in one place; remember that parseInt still throws NumberFormatException on size=abc, so map that to a 400 rather than letting it become a 500. When the default comes from a remote call or a database, reach for orElseGet so the happy path stays fast. If there is no sensible default and a missing value is an error, use orElseThrow instead of inventing one.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Applying a default page size or sort order when the query string leaves it out.
  • Falling back to a value loaded from the database or a remote config service only when the cached value is missing.
  • Passing an absent optional field to JDBC or a legacy library as null with orElse(null).

Frequently asked questions

What is the difference between orElse and orElseGet in Java?

orElse(value) receives an already computed value, so its argument is evaluated even when the Optional has a value. orElseGet(supplier) only calls the supplier when the Optional is empty. Use orElseGet when the fallback is expensive or has side effects.

Why is my orElse method always called?

Because Java evaluates method arguments before the call. In optional.orElse(loadDefault()), loadDefault() runs first, then orElse decides whether to use it. Switch to orElseGet(() -> loadDefault()).

Can I use orElse(null)?

Yes. orElse accepts null and returns it when the Optional is empty. It is the usual way to convert an Optional back to a nullable reference for older APIs.

More examples

Default page size from a query string

A literal default is cheap, so orElse is the right call. The missing size parameter flows through map untouched as an empty Optional and lands on the default. A non-numeric size would still throw NumberFormatException from parseInt, which a real handler turns into a 400.

Example · java
int pageSize(Map<String, String> query) {
    return Optional.ofNullable(query.get("size"))
            .map(Integer::parseInt)
            .orElse(20);
}

void main() {
    IO.println(pageSize(Map.of("size", "50")));
    IO.println(pageSize(Map.of("page", "2")));
}
// Output:
// 50
// 20

orElse(null) for an API that expects null

orElse accepts null, which is how you hand 'no value' to older code that expects null, such as a JDBC parameter that should be stored as SQL NULL. The blank nickname is filtered out, so it becomes null too.

Example · java
record User(String name, String nickname) {}

Optional<String> nickname(User u) {
    return Optional.ofNullable(u.nickname()).filter(n -> !n.isBlank());
}

void main() {
    String a = nickname(new User("Ada", "countess")).orElse(null);
    String b = nickname(new User("Linus", "  ")).orElse(null);
    IO.println(a);
    IO.println(b);
}
// Output:
// countess
// null

orElseGet avoids creating duplicate records

With orElseGet the second call finds the cart and never creates one. The orElse version calls createCart before looking at the Optional, so it still returns cart 1 but has already replaced the stored cart with cart 2.

Example · java
record Cart(String userId, int number) {}

final Map<String, Cart> carts = new LinkedHashMap<>();
int created = 0;

Cart createCart(String userId) {
    created++;
    Cart cart = new Cart(userId, created);
    carts.put(userId, cart);
    IO.println("created cart " + created + " for " + userId);
    return cart;
}

Cart cartFor(String userId) {
    return Optional.ofNullable(carts.get(userId)).orElseGet(() -> createCart(userId));
}

Cart buggyCartFor(String userId) {
    return Optional.ofNullable(carts.get(userId)).orElse(createCart(userId));
}

void main() {
    IO.println(cartFor("u1"));
    IO.println(cartFor("u1"));
    IO.println(buggyCartFor("u1"));
    IO.println(carts);
}
// Output:
// created cart 1 for u1
// Cart[userId=u1, number=1]
// Cart[userId=u1, number=1]
// created cart 2 for u1
// Cart[userId=u1, number=1]
// {u1=Cart[userId=u1, number=2]}

Discussion

  • Be the first to comment on this lesson.