Definition
Optional is a container that holds either one value or nothing, forcing the caller to handle the empty case.
- Optional
– a box that is either present with a value or empty, replacing a raw nullreturn. - filter(predicate).findAny() – keeps only matching employees and returns the first one it finds as an
Optional. - orElse(fallback) – returns the value if present, otherwise the fallback, but the fallback expression is always evaluated.
- orElseGet(supplier) – returns the value if present, otherwise calls the supplier, which runs only when the Optional is empty.
- orElseThrow(supplier) – returns the value if present, otherwise throws the exception you build in the supplier.
At the National Bank of India, Priya asks the teller for any available HR officer to approve her form. The teller checks the duty roster, which is like an Optional: it either names an officer or it is blank. If an officer is on duty, the teller calls that person (the value is present). If the roster is blank, orElse would be like always phoning the backup office even when an officer was free, wasting the call, while orElseGet only phones the backup when the roster is truly empty. orElseThrow is the teller refusing to proceed and raising a formal complaint when no officer exists at all.
Understanding the Concepts
What does findAny return and why is it an Optional?
When you search a stream for a matching element, the match might not exist. The HR department could be empty, or every employee could be in another department. Java models this possibly-absent result with Optional instead of returning null, so the caller is forced to think about the empty case.
package com.bank.hr;
import java.util.List;
import java.util.Optional;
public class EmployeeFinder {
public Optional<Employee> findAnyFromHr(List<Employee> employees) {
return employees.stream()
.filter(e -> e.getDepartment().equals("HR"))
.findAny();
}
}
The return type Optional tells every caller: this method may give you an employee, or it may give you nothing, and you must decide what to do in each case.
How do you define the Employee model?
The employee is a plain model class. Salary uses BigDecimal because it represents money.
package com.bank.hr;
import java.math.BigDecimal;
public class Employee {
private final String name;
private final String department;
private final BigDecimal salary;
public Employee(String name, String department, BigDecimal salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
public String getName() { return name; }
public String getDepartment() { return department; }
public BigDecimal getSalary() { return salary; }
@Override
public String toString() {
return name + " (" + department + ")";
}
}
How do orElse, orElseGet, and orElseThrow unwrap the Optional?
Once you hold an Optional, you need a value out of it. The three methods differ in what happens when the Optional is empty.
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
public class UnwrapDemo {
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("Rajesh", "Engineering", new BigDecimal("120000")),
new Employee("Priya", "HR", new BigDecimal("95000"))
);
EmployeeFinder finder = new EmployeeFinder();
// 1. orElse: return a ready default when empty
Employee a = finder.findAnyFromHr(staff)
.orElse(new Employee("Nobody", "HR", BigDecimal.ZERO));
// 2. orElseGet: build the default lazily only if empty
Employee b = finder.findAnyFromHr(staff)
.orElseGet(() -> new Employee("Nobody", "HR", BigDecimal.ZERO));
// 3. orElseThrow: treat absence as an error
Employee c = finder.findAnyFromHr(staff)
.orElseThrow(() -> new IllegalStateException("No HR employee found"));
System.out.println(a);
System.out.println(b);
System.out.println(c);
}
}
Priya (HR)
Priya (HR)
Priya (HR)
What is the real difference between orElse and orElseGet?
This is the most common Optional interview question. orElse(x) always evaluates x, even when the Optional has a value and the fallback is never used. orElseGet(supplier) evaluates the supplier only when the Optional is empty. When the fallback is cheap this makes no difference, but when the fallback hits a database or creates an object, the eager version does needless work.
package com.bank.hr;
import java.util.Optional;
public class EagerVsLazy {
static String expensiveDefault() {
System.out.println("expensiveDefault() was called");
return "DEFAULT";
}
public static void main(String[] args) {
Optional<String> present = Optional.of("Priya");
System.out.println("-- orElse --");
String a = present.orElse(expensiveDefault()); // runs even though value is present
System.out.println("result = " + a);
System.out.println("-- orElseGet --");
String b = present.orElseGet(EagerVsLazy::expensiveDefault); // skipped, value present
System.out.println("result = " + b);
}
}
-- orElse --
expensiveDefault() was called
result = Priya
-- orElseGet --
result = Priya
Notice the message prints under orElse even though the value is present, but not under orElseGet.
How do orElse and orElseGet compare at a glance?
| Aspect | orElse(value) | orElseGet(supplier) |
|--------|---------------|---------------------|
| Argument type | a ready value | a `Supplier<T>` |
| When the fallback runs | always | only when empty |
| Cost if value present | evaluates fallback anyway | zero extra work |
| Best for | cheap constants | expensive or side-effecting defaults |
| Example | `opt.orElse("")` | `opt.orElseGet(() -> loadFromDb())` |The rule: use orElse for a plain constant, and orElseGet whenever building the default costs anything.
What does orElseThrow do and which form should you use?
orElseThrow() with no argument throws NoSuchElementException. orElseThrow(supplier) throws whatever exception you build, which lets you raise a meaningful domain exception that the rest of the system understands.
package com.bank.hr;
import java.util.List;
import java.util.Optional;
public class ThrowDemo {
public static void main(String[] args) {
List<Employee> noHr = List.of(); // empty: no HR employee
Optional<Employee> result = noHr.stream()
.filter(e -> e.getDepartment().equals("HR"))
.findAny();
try {
Employee e = result.orElseThrow(
() -> new IllegalStateException("HR department has no staff"));
System.out.println(e);
} catch (IllegalStateException ex) {
System.out.println("Caught: " + ex.getMessage());
}
}
}
Caught: HR department has no staff
What are common mistakes with Optional?
The worst mistake is calling get() without checking presence, which defeats the whole purpose of Optional and throws on empty.
// WRONG: get() throws NoSuchElementException when empty
Employee e = finder.findAnyFromHr(staff).get();
// WRONG: checking isPresent then get is verbose and loses the benefit
Optional<Employee> opt = finder.findAnyFromHr(staff);
if (opt.isPresent()) {
Employee e2 = opt.get();
process(e2);
}
// CORRECT: hand the value straight to a consumer, no manual check
finder.findAnyFromHr(staff)
.ifPresent(this::process);
// CORRECT: provide a fallback inline
Employee safe = finder.findAnyFromHr(staff)
.orElseThrow(() -> new IllegalStateException("No HR employee"));
The correct versions never risk an exception and read as a single statement.
How is findAny different from findFirst?
Both return an Optional, but findFirst always returns the first element in encounter order, while findAny may return any matching element. On a sequential stream they usually behave the same, but on a parallel stream findAny can be much faster because it does not force ordering.
| Method | Returns | Ordering guarantee | Parallel performance |
|--------|---------|--------------------|-----------------------|
| findFirst() | first matching element | deterministic, encounter order | may be slower |
| findAny() | any matching element | none | often faster |
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
public class FindAnyVsFirst {
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("Priya", "HR", new BigDecimal("95000")),
new Employee("Anita", "HR", new BigDecimal("98000"))
);
Optional<Employee> first = staff.stream()
.filter(e -> e.getDepartment().equals("HR"))
.findFirst();
Optional<Employee> any = staff.parallelStream()
.filter(e -> e.getDepartment().equals("HR"))
.findAny();
System.out.println("findFirst = " + first.orElse(null));
System.out.println("findAny = " + any.orElse(null));
}
}
findFirst = Priya (HR)
findAny = Priya (HR)
Use findFirst when order matters, and findAny when any match will do and you want parallel speed.
How do you transform the value inside an Optional?
You often do not need the whole employee, only one field. map transforms the value if present and stays empty otherwise, so you never touch a null.
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
public class MapDemo {
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("Priya", "HR", new BigDecimal("95000"))
);
String name = staff.stream()
.filter(e -> e.getDepartment().equals("HR"))
.findAny()
.map(Employee::getName)
.orElse("UNKNOWN");
System.out.println("HR contact: " + name);
}
}
HR contact: Priya
What are the edge cases to watch for?
An Optional can wrap a value, but it should never itself be null, and it should not wrap another Optional. Returning null where an Optional is expected breaks every caller.
// WRONG: returning null instead of an empty Optional
public Optional<Employee> broken(List<Employee> list) {
if (list.isEmpty()) {
return null; // callers will NPE on .orElse(...)
}
return list.stream().findAny();
}
// CORRECT: return Optional.empty(), never null
public Optional<Employee> safe(List<Employee> list) {
return list.stream()
.filter(e -> e.getDepartment().equals("HR"))
.findAny();
}
Also note Optional.of(x) throws if x is null, while Optional.ofNullable(x) returns empty for null. Use ofNullable when the source might be null.
When should you use Optional and when should you avoid it?
Use Optional as the return type of a method whose result may legitimately be absent, like a lookup. Do not use it as a field, a constructor parameter, or a method argument, because it adds overhead and complicates serialization without buying clarity.
// GOOD: Optional as a return type for a lookup
public Optional<Employee> findManager(String department) { ... }
// AVOID: Optional as a field
public class Account {
private Optional<String> nickname; // use a plain nullable String instead
}
What are common interview follow-up questions?
Interviewers often ask you to contrast orElse and orElseGet with a cost argument, explain why get() is discouraged, and show ifPresentOrElse from Java 9 which handles both branches at once.
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
public class IfPresentOrElseDemo {
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("Priya", "HR", new BigDecimal("95000"))
);
staff.stream()
.filter(e -> e.getDepartment().equals("HR"))
.findAny()
.ifPresentOrElse(
e -> System.out.println("Found " + e.getName()),
() -> System.out.println("No HR employee on record"));
}
}
Found Priya
Visual Overview
List<Employee>
│
│ stream()
▼
Stream<Employee>
│
│ filter(e -> e.getDepartment().equals("HR"))
▼
Stream<Employee> (only HR)
│
│ findAny()
▼
┌───────────────────────────────┐
│ Optional<Employee> │
│ │
│ present ? │
└───────────────────────────────┘
│ │
present empty
│ │
▼ ▼
return value pick a fallback path
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
orElse(value) orElseGet(supplier) orElseThrow(supplier)
(eager default) (lazy default) (raise exception)
orElse vs orElseGet when value IS present
opt = Optional.of("Priya")
opt.orElse( build() ) opt.orElseGet( () -> build() )
│ │
▼ ▼
build() RUNS build() SKIPPED
(wasted work) (lazy, efficient)
│ │
▼ ▼
returns "Priya" returns "Priya"
Key Takeaway
At the National Bank of India, Rajesh needs to find any HR officer from a list of staff. The officer might not be on the list, so instead of returning a raw value that could be null, the search returns an Optional. That box either holds an officer or is empty, and it forces Rajesh to decide what happens when nobody is found.
When the box holds a value, all three unwrapping methods return it. The difference shows up only when the box is empty. orElse hands back a value you prepared in advance, but it prepares that value every time, even when the box was full, so it wastes work if the fallback is expensive. orElseGet takes a supplier and runs it only when the box is empty, which is why it is the better choice whenever building the default costs anything.
When absence is not a normal outcome but a real error, orElseThrow stops the flow and raises an exception. The no-argument form throws NoSuchElementException, and the supplier form lets Rajesh throw a domain exception that the rest of the banking system understands.
The mistakes to avoid are calling get() without checking, returning null instead of Optional.empty(), and reaching for Optional as a field or parameter. Used as a return type for a lookup that may find nothing, Optional makes the empty case impossible to ignore.
Remember one sentence: filter then findAny gives you an Optional, orElse evaluates its default eagerly while orElseGet evaluates lazily, and orElseThrow turns an empty result into a clear error.
---