Back to Optional
Q8Question 8 of 9

Find any employee from HR using Optional

Key Concepts
  • filter(e -> e.getDepartment().equals("HR")).findAny() returns an Optional<Employee> that is empty when no HR employee exists
  • orElse(value) always evaluates its argument, even when the Optional has a value, so avoid it when the fallback is expensive
  • orElseGet(supplier) only runs the supplier when the Optional is empty, making it the lazy and preferred fallback
  • orElseThrow() throws NoSuchElementException, while orElseThrow(supplier) lets you throw a domain exception you choose
  • findAny() may return any matching element and is faster on parallel streams, while findFirst() is deterministic
  • Never call Optional.get() without checking isPresent() first, as it throws NoSuchElementException on an empty Optional
  • Optional is designed as a return type for possibly-absent results, not as a field type or method parameter

Definition

Use stream filter plus findAny to get an Optional<Employee>, then unwrap it with orElse for an eager default, orElseGet for a lazy default, or orElseThrow when absence is an error.

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 null return.
  • 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.

JAVA
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.

JAVA
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.

JAVA
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);
    }
}
TEXT
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.

JAVA
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);
    }
}
TEXT
-- 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?

DIAGRAM
| 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.

JAVA
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());
        }
    }
}
TEXT
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.

JAVA
// 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);
}
JAVA
// 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 |

JAVA
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));
    }
}
TEXT
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.

JAVA
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);
    }
}
TEXT
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.

JAVA
// 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.

JAVA
// 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.

JAVA
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"));
    }
}
TEXT
Found Priya

Visual Overview

DIAGRAM
  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)
DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate