Optional Methods in Java
Read this snippet and predict the console output:
public class OptionalDemo1 {
public static void main(String[] args) {
Optional<String> summary = Optional.of("Optional methods in Java");
System.out.println(summary.orElse(defaultSummary()));
}
static String defaultSummary() {
System.out.println("Building the default value...");
return "Default summary";
}
} The Optional holds a value, so defaultSummary() looks unnecessary — yet two lines are printed: first Building the default value..., then Optional methods in Java. The argument of orElse() is an ordinary expression, and Java evaluates it before the method is even entered, no matter whether the container is empty. Swap the call for summary.orElseGet(OptionalDemo1::defaultSummary) and the extra line disappears.
This lesson continues the topic started in «What Is Optional in Java», where the purpose of the class and the three factory methods (empty(), of(), ofNullable()) are covered. Here we go straight to the API: what every method does, what it returns, and when to reach for it.
The methods of Optional<T> fall into four groups:
- Creation —
empty(),of(),ofNullable(): static factories that return anOptional. - Presence checks —
isPresent(),isEmpty(),ifPresent(),ifPresentOrElse(). - Unwrapping —
get(),orElse(),orElseGet(),orElseThrow(): they return the value of typeT, not the wrapper. - Transformation —
map(),flatMap(),filter(),or(),stream(): they return a newOptional(or aStream), which is why they chain.
1. All Optional Methods: Reference Table
java.util.Optional arrived in Java 8 and has been extended in almost every major release since. The highlighted rows are methods that do not exist in Java 8 — if your code must compile against an older release, they are off the table.
| Method | What it does | When to use it |
|---|---|---|
empty() | Returns an empty Optional | When there is knowingly no value: a lookup that found nothing |
of(T value) | Wraps a value; null throws NullPointerException | When the value is guaranteed not to be null |
ofNullable(T value) | Wraps a value, or returns an empty Optional for null | When the value may be null: a database row, a Map lookup |
isPresent() | Returns true if a value is present | In a condition, when you need the check itself and not the value |
isEmpty() (Java 11+) | Returns true if there is no value | Instead of !optional.isPresent() — easier to read |
ifPresent(Consumer) | Runs an action if a value is present, does nothing otherwise | Instead of the if (isPresent()) { get() } pair |
ifPresentOrElse(Consumer, Runnable) (Java 9+) | One action for the value, a separate action for the empty case | When you need both branches: handle the value or log its absence |
get() | Returns the value or throws NoSuchElementException | Only after an explicit check; in new code prefer orElseThrow() |
orElse(T other) | Returns the value or the object you passed in | When the fallback already exists: a constant, a literal, an empty list |
orElseGet(Supplier) | Returns the value or a lazily computed fallback | When the fallback is expensive: a query, a file read, a new call |
orElseThrow() (Java 10+) | Returns the value or throws NoSuchElementException | A self-documenting replacement for get(): the name warns you |
orElseThrow(Supplier) | Returns the value or throws the exception your supplier builds | When you need your own exception: UserNotFoundException and friends |
filter(Predicate) | Keeps the value if it passes the test, otherwise yields an empty Optional | An extra condition inside a chain, without an if |
map(Function) | Applies a function to the value and rewraps the result in an Optional | When the function returns a plain object: String, Integer |
flatMap(Function) | Same, but the function itself returns an Optional, so no extra wrapper appears | Chains where every step already returns an Optional |
or(Supplier<Optional>) (Java 9+) | If empty, substitutes an Optional from another source | A fallback chain: cache, then file, then configuration |
stream() (Java 9+) | Turns the container into a Stream of zero or one element | Inside flatMap(), to drop empty values from a stream |
equals(Object) | Compares the contents of two containers using the values' equals() | Tests and result comparison |
hashCode() | Hash of the value, or zero for an empty container | Rarely called directly |
toString() | Produces Optional[value] or Optional.empty | Debugging and logs — never user-facing output |
Exact signatures and the precise contract wording are always available in the official javadoc for java.util.Optional.
2. Presence Checks: isPresent, isEmpty, ifPresent, ifPresentOrElse
isPresent() returns true when the container is not empty. It is the most literal check available — and the closest thing to a plain if (value != null).
Optional<String> summary = Optional.of("Optional methods in Java");
Optional<String> emptySummary = Optional.empty();
if (summary.isPresent()) {
System.out.println(summary.get()); // Optional methods in Java
}
System.out.println(summary.isPresent()); // true
System.out.println(emptySummary.isPresent()); // false Java 11 added the mirror method isEmpty(). It brings no new capability, but it removes a negation that is easy to skim over:
if (emptySummary.isEmpty()) { // instead of if (!emptySummary.isPresent())
System.out.println("No value here"); // No value here
} ifPresent(Consumer) executes the given action only when a value exists. For an empty container nothing at all happens — no exception, no output:
summary.ifPresent(System.out::println); // Optional methods in Java
emptySummary.ifPresent(System.out::println); // silence, and no NullPointerException Need the second branch too? Before Java 9 that meant wrapping isPresent() in an if/else. Now there is ifPresentOrElse(Consumer, Runnable): the first argument receives the value, the second runs when there is none.
summary.ifPresentOrElse(
s -> System.out.println("Found: " + s),
() -> System.out.println("Value is not set"));
// Found: Optional methods in Java
emptySummary.ifPresentOrElse(
s -> System.out.println("Found: " + s),
() -> System.out.println("Value is not set"));
// Value is not set Note the argument types: Consumer<T> accepts the value, while Runnable accepts nothing — there is nothing to accept, the container is empty.
3. Unwrapping: get, orElse, orElseGet, orElseThrow
All four return an object of type T, which means they take you out of Optional land and back into ordinary code. What differs is their behaviour when the container is empty.
get()
get() hands back the value as is and throws NoSuchElementException on an empty container:
Optional<String> emptySummary = Optional.empty();
System.out.println(emptySummary.get());
// Exception in thread "main" java.util.NoSuchElementException: No value present This is exactly why calling get() without a preceding check is pointless: it converts a NullPointerException into a NoSuchElementException and saves you from nothing.
orElse(T other)
Returns the value, or the object you passed in if the container is empty:
Optional<String> summary = Optional.of("Optional methods in Java");
Optional<String> emptySummary = Optional.empty();
System.out.println(summary.orElse("Default summary")); // Optional methods in Java
System.out.println(emptySummary.orElse("Default summary")); // Default summary null is a legal fallback here: optional.orElse(null) is a valid way to step back to a plain reference, for instance when feeding the result to a legacy API.
orElseGet(Supplier)
The same idea, except the fallback is described by a function and is computed only at the moment it is actually needed:
System.out.println(emptySummary.orElseGet(() -> "Default summary")); // Default summary orElseThrow()
Since Java 10 Optional has a no-argument orElseThrow(). It does precisely what get() does, but the method name makes the risk visible at the call site:
String value = summary.orElseThrow(); // Optional methods in Java
String fail = emptySummary.orElseThrow();
// java.util.NoSuchElementException: No value present orElseThrow(Supplier)
The overload with an argument lets you throw your own exception with a meaningful message — the standard way to turn «nothing found» into a business error:
public User findUser(long id) {
return repository.findById(id) // Optional<User>
.orElseThrow(() -> new UserNotFoundException("User " + id + " not found"));
} Worth knowing
The get() method is not marked @Deprecated, so the compiler stays quiet about it. Since Java 10, however, the javadoc names orElseThrow() as the preferred alternative. Use orElseThrow() in new code and keep get() for reading legacy projects — and for interview questions.
4. orElse vs orElseGet: The Real Difference
Both methods produce the same value, and in 99% of cases the result is identical. What differs is when the fallback is computed:
orElse(T other)takes a ready-made object. The argument is evaluated eagerly, before the method is entered — even when theOptionalholds a value and the result will be discarded.orElseGet(Supplier<T>)takes a function. It is invoked lazily, only when the container turns out to be empty.
As long as the right-hand side of orElse() is a literal or a constant, nothing separates them. The gap opens the moment a method call appears there:
public class OptionalDemo2 {
public static void main(String[] args) {
Optional<String> summary = Optional.of("Optional methods in Java");
System.out.println("--- orElse ---");
System.out.println(summary.orElse(defaultSummary()));
// Building the default value...
// Optional methods in Java
System.out.println("--- orElseGet ---");
System.out.println(summary.orElseGet(OptionalDemo2::defaultSummary));
// Optional methods in Java
}
static String defaultSummary() {
System.out.println("Building the default value...");
return "Default summary";
}
} orElse() called defaultSummary() and threw the result away. Here the cost is one stray console line, but in real code that slot often holds a database query, a remote call, or the construction of a heavyweight object — work whose result nobody will ever use. It gets worse when the method mutates state: writes a log entry, bumps a counter, inserts a row. That side effect happens for a non-empty Optional too.
The working rule: orElse() for values that already exist (orElse(""), orElse(0), orElse(List.of())), orElseGet() for anything that has to be computed (orElseGet(this::loadDefaults), orElseGet(ArrayList::new)).
Interview question
«What is the difference between orElse() and orElseGet()?» is the single most common Optional question. Answering «orElseGet takes a Supplier» rarely scores: the interviewer is waiting for the point that the argument of orElse() is evaluated every time, even when the value is present, and that with an expensive or state-changing call this means wasted work and unplanned side effects.
5. Transformation: map, flatMap, filter
These three return a new Optional rather than a value, so you can chain them and close the chain with a single orElse() or orElseThrow().
map(Function)
map() applies the function to the value if one is present and rewraps the result into an Optional. On an empty container the function is never invoked:
Optional<String> summary = Optional.of("Optional methods in Java");
Optional<String> emptySummary = Optional.empty();
System.out.println(summary.map(s -> s.length()).orElse(0)); // 24
System.out.println(emptySummary.map(s -> s.length()).orElse(0)); // 0
Optional<Integer> length = summary.map(String::length);
System.out.println(length); // Optional[24] The type changes: Optional<String> became Optional<Integer>. One more detail matters here: if the function returns null, map() does not blow up — it returns an empty Optional, because internally it uses ofNullable().
filter(Predicate)
filter() keeps the value when it satisfies the predicate and empties the container when it does not:
Optional<String> summary = Optional.of("Optional methods in Java");
Optional<String> shortSummary = Optional.of("Java");
System.out.println(summary.filter(s -> s.length() > 10).orElse("Short summary"));
// Optional methods in Java
System.out.println(shortSummary.filter(s -> s.length() > 10).orElse("Short summary"));
// Short summary On an empty Optional the predicate is not evaluated at all — the result is empty right away.
flatMap(Function)
flatMap() is what you need when the function itself returns an Optional. A plain map() would produce a double wrapper, Optional<Optional<T>>, which is awkward to unpack:
// getAddress() returns Optional<Address>
Optional<Optional<Address>> wrapped = user.map(User::getAddress); // double wrapper
Optional<Address> address = user.flatMap(User::getAddress); // what you wanted flatMap() is what holds together chains of nested calls that used to be written as a staircase of null checks:
String city = repository.findById(42) // Optional<User>
.flatMap(User::getAddress) // Optional<Address>, the method returns Optional
.map(Address::getCity) // Optional<String>, the method returns String
.filter(c -> !c.isBlank())
.orElse("city not specified"); The selection rule is short: if the method returns a plain object, use map(); if it already returns an Optional, use flatMap().
6. or() and stream()
Both appeared in Java 9 and cover scenarios that previously required hand-written glue code.
or(Supplier<Optional>)
or() differs from orElseGet() in its return type: it gives back an Optional, not the value, so calls can be stacked one after another:
Optional<String> empty = Optional.empty();
System.out.println(empty.or(() -> Optional.of("fallback"))); // Optional[fallback]
System.out.println(Optional.of("value").or(() -> Optional.of("fallback"))); // Optional[value] The typical use case is a chain of sources: read a setting from the cache, fall back to a file, then to environment variables:
String mode = readFromCache() // Optional<String>
.or(this::readFromFile) // Optional<String>
.or(this::readFromEnvironment) // Optional<String>
.orElse("default"); Like orElseGet(), or() is lazy: the next source is not touched once a previous one has produced a value.
stream()
stream() turns the container into a stream of one element — or an empty stream when there is no value. On its own it looks useless, but combined with flatMap() it removes an entire boilerplate pattern:
List<Optional<String>> results = List.of(
Optional.of("Java"), Optional.empty(), Optional.of("Optional"));
List<String> values = results.stream()
.flatMap(Optional::stream)
.toList(); // [Java, Optional]
// before Java 9 the same thing took two steps:
List<String> old = results.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList()); 7. OptionalInt, OptionalLong, OptionalDouble
Primitives have their own containers: OptionalInt, OptionalLong and OptionalDouble. They avoid boxing into wrapper objects and are what the primitive stream methods return (average(), max(), findFirst()).
OptionalInt optionalInt = OptionalInt.of(1);
System.out.println(optionalInt); // OptionalInt[1]
System.out.println(optionalInt.getAsInt()); // 1
System.out.println(OptionalInt.empty().orElse(0)); // 0
OptionalDouble average = IntStream.of(28, 4, 91, 30).average();
System.out.println(average); // OptionalDouble[38.25]
System.out.println(average.getAsDouble()); // 38.25
OptionalLong optionalLong = OptionalLong.of(10L);
optionalLong.ifPresent(value -> System.out.println(value)); // 10 Instead of get() these classes expose getAsInt(), getAsLong() and getAsDouble(), which return a primitive. Their API is noticeably thinner than that of Optional<T>: you get isPresent(), isEmpty(), ifPresent(), ifPresentOrElse(), orElse(), orElseGet(), orElseThrow() and stream(), but there is no map(), filter(), flatMap() or or(). If you need transformations, move to the object version: OptionalInt.of(1).stream().boxed().findFirst() gives you an Optional<Integer>, which has them all.
8. equals(), hashCode() and toString()
Optional overrides all three Object methods. equals() compares the contents of the containers, not the references to them:
System.out.println(Optional.of("Java").equals(Optional.of("Java"))); // true
System.out.println(Optional.empty().equals(Optional.empty())); // true
System.out.println(Optional.of("Java").equals(Optional.empty())); // false
System.out.println(Optional.of("Java").equals("Java")); // false The last line is the important one: a container is never equal to the value it holds. hashCode() returns the hash of the value, and 0 for an empty container.
toString() gives a debugging-friendly representation that must never reach the end user:
System.out.println(Optional.of("Java")); // Optional[Java]
System.out.println(Optional.empty()); // Optional.empty If Optional[38.25] shows up in a console or a log instead of a number, somebody forgot to unwrap the container with orElse(), orElseThrow() or getAsDouble().
9. Where Optional Is Easy to Get Wrong
- The argument of
orElse()is always evaluated — even when the container holds a value. For expensive computations and methods with side effects, useorElseGet(). get()without a check is aNullPointerExceptionin disguise. Reach fororElse(),orElseGet()ororElseThrow()with a message of your own instead.Optionalas a field or a method parameter is an anti-pattern. The class was designed as a return type for methods that may find nothing. As a field it adds an extra object per instance and breaks serialization:Optionaldoes not implementSerializable. As a parameter it forces callers to writeOptional.of(x)and introduces a third state —nullin place of theOptionalitself. Use method overloading for optional parameters, and a plain field with a getter that returnsOptionalfor state.isPresent()plusget()is no better than anullcheck — only longer. Idiomatic code gets by withifPresent(),map(),filter()andorElse().map()swallowsnull. If the function insidemap()returnsnull, there is no exception — you end up with an emptyOptional. Convenient sometimes, a source of silently lost values at other times.- Do not store
Optionalin collections. AList<Optional<String>>almost always means the empty elements should have been dropped earlier, for example withflatMap(Optional::stream). Optionaldoes not protect you fromnullby itself. A variable of typeOptional<String>can still benullif somebody assignednullto it. A method that returnsOptionalmust returnOptional.empty(), nevernull.
Rule of thumb
A healthy sign that Optional is being used as intended: the container is created inside a method, lives for a few lines, and disappears at an orElse() or an ifPresent(). Once an Optional travels into an object field, a constructor parameter or a collection, a plain reference with an explicit check is almost always the simpler design.
Frequently Asked Questions
What is the difference between orElse and orElseGet in Java?
They return the same value; they differ in when the fallback is computed. orElse takes a ready-made object, and that expression is evaluated every time, even when the Optional holds a value and the result is discarded. orElseGet takes a Supplier and calls it only when the container is empty. With a literal or a constant on the right-hand side there is no difference; with a method call, a database query or object creation, choose orElseGet - otherwise the program does useless work and may trigger a side effect such as a log entry.
Why does get() throw NoSuchElementException and what should replace it?
An empty Optional holds nothing, so get() has nothing to return and throws NoSuchElementException with the message No value present. Since Java 10 the javadoc names orElseThrow as the preferred alternative: the same result, but the method name shows that the call can end in an exception. If failing is not an option, use orElse, orElseGet, or orElseThrow with your own exception.
Can Optional be used as a class field or a method parameter?
It is discouraged. Optional was designed as a return type for methods that may find nothing. As a field it adds an extra object per instance and breaks serialization, because Optional does not implement Serializable. As a parameter it forces the caller to wrap the argument and allows a third state, where null is passed instead of the Optional itself. For optional parameters use method overloading, and for state keep a plain reference and return Optional from the getter.
What is the difference between map and flatMap in Optional?
map applies a function to the value and wraps the result into an Optional itself. flatMap applies a function that already returns an Optional and does not add a second wrapper. Calling map on a method that returns Optional produces an Optional inside an Optional, which is awkward to unpack. The rule is simple: the method returns a plain object - use map; the method returns an Optional - use flatMap.
How do I check that an Optional is empty without negating isPresent?
Java 11 added isEmpty, which returns true for an empty container, so a condition with a negation in front of isPresent becomes a condition with a call to isEmpty. If you need an action for both the value and its absence, Java 9 added ifPresentOrElse: the first argument, a Consumer, receives the value, and the second argument, a Runnable, runs when there is no value.
Comments