findFirst vs findAny and short-circuiting
findFirst versus findAny, encounter order, sequential and parallel behavior, Optional edge cases, match terminals, limit, infinite streams and short-circuiting traps.
On this page
🔎 What are
findFirst, findAny and short-circuiting?
findFirst() and findAny() are terminal operations that return an Optional<T> containing one element from a stream, if one exists. They are short-circuiting because they can stop traversal once the answer is known. Think of a warehouse search: findFirst means "bring me the first box on the route", while findAny means "bring me any matching box you can reach soonest."
For this lesson, we'll focus on the choice that comes up in interviews and production reviews: encounter order, sequential versus parallel behavior, why findFirst can be expensive on ordered parallel streams, how match operations stop early, and how short-circuiting lets infinite streams terminate. We'll keep the deeper pipeline internals in How a stream pipeline executes and the operation catalogue in Intermediate vs terminal operations.
Below the line (out of scope)
- Stream pipeline execution internals: How a stream pipeline executes
- Operation categories and the one-shot stream rule: Intermediate vs terminal operations
- Optional as a type and API design choice: A guide to Java Optional
- Parallel stream tuning and common-pool behavior: Parallel streams: when they help and hurt
The 30-second answer
findFirst() respects encounter order: on an ordered stream it returns the first element that reaches the terminal operation. findAny() is allowed to return any element, which makes it a better fit for parallel searches when the business rule does not care which match wins. In sequential pipelines, findAny() often returns the same value as findFirst(), but the API does not promise that, so do not build correctness on it. Both return Optional<T>, throw NullPointerException if the selected element is null, and can stop early. anyMatch, allMatch, noneMatch and limit are short-circuiting too; that is why Stream.iterate(...).filter(...).findFirst() can finish, while sorted().findFirst() over an unbounded stream cannot.
What you’ll learn
- Explain encounter order and why
findFirst()promises more thanfindAny() - Choose
findAny()for parallel searches when any matching element is acceptable - Describe the parallel cost of
findFirst()on ordered streams and whenunordered()helps - Use
Optional<T>results safely, including theNullPointerExceptiontrap for null elements - Reason about
anyMatch,allMatch,noneMatch,limitand infinite streams - Avoid
sorted().findFirst()whenmin()gives the same answer in O(n) time
01Picture it first: first shelf versus any shelf
Picture four workers searching shelves in parallel. If the rule is "first shelf in aisle order," everyone must coordinate to prove no earlier shelf has a match. If the rule is "any matching shelf," the first worker who finds one can call it in.
That is the practical difference. findFirst buys determinism; findAny buys freedom. Freedom is often what parallel execution needs to stop sooner.
findFirst waits for the earliest ordered match while findAny can accept whichever parallel worker finds a matching element first
Encounter order is the order a stream source defines for traversal. A List has encounter order. A LinkedHashSet has insertion order. A plain HashSet has no guaranteed encounter order, even if it looks stable in one small run.
02findFirst versus findAny under the hood
Try it yourself first
Before reading the output, predict both lines. Then ask yourself whether the findAny() line is a promise or an accident of this sequential run.
import java.util.List;
final class FindSequentialDemo {
public static void main(String[] args) {
List<String> statuses = List.of("created", "paid", "refunded", "paid");
System.out.println(statuses.stream()
.filter("paid"::equals)
.findFirst()
.orElse("none"));
System.out.println(statuses.stream()
.filter("paid"::equals)
.findAny()
.orElse("none"));
}
}
A typical Java 21 output is:
paid
paid
Both print paid, but that example hides the important part because the values are identical. In a sequential stream, findAny() will usually behave like findFirst() in the current JDK implementation. The contract still says any element, so your code should treat the choice as nondeterministic.
Use different matching values and the rule becomes clearer:
import java.util.List;
final class FindFirstByValueDemo {
public static void main(String[] args) {
List<String> names = List.of("Asha", "Ben", "Anika", "Amir");
String firstA = names.stream()
.filter(name -> name.startsWith("A"))
.findFirst()
.orElseThrow();
System.out.println(firstA); // Asha
}
}
findFirst() means the first matching element in encounter order, not the smallest, not the fastest to compute, and not a random match. If the source is unordered, findFirst() has no useful order to preserve, so it behaves like "some element" from that unordered source.
03Parallel streams: determinism has a price
Parallel streams split the source into chunks. For findAny(), any worker that finds a match may complete the operation. For findFirst() on an ordered stream, the implementation has to respect the leftmost matching element in encounter order, which can require coordination with earlier chunks.
Try it yourself first
Run this a few times. Which terminal operation is allowed to print a value that is not 0?
import java.util.stream.IntStream;
final class ParallelFindDemo {
public static void main(String[] args) {
int first = IntStream.range(0, 1_000_000)
.parallel()
.filter(n -> n % 100_000 == 0)
.findFirst()
.orElseThrow();
int any = IntStream.range(0, 1_000_000)
.parallel()
.filter(n -> n % 100_000 == 0)
.findAny()
.orElseThrow();
System.out.println("first=" + first);
System.out.println("any=" + any);
}
}
first must be 0, because IntStream.range is ordered and 0 is the first matching value. any may be 0, 300000, 600000 or another matching value depending on how work is split and scheduled. If your machine often prints 0 for both, that is fine; it is not a contract.
This is the decision I use in code review:
| Business rule | Stream shape | Operation I choose |
|---|---|---|
| Need the first event by input order | Ordered sequential or ordered parallel | findFirst() |
| Need any valid cache entry | Sequential | findAny() is fine, findFirst() is clearer if order matters to readers |
| Need any valid cache entry | Parallel | findAny() |
| Source order is irrelevant | Parallel with order constraint removed | unordered().filter(...).findAny() |
| Need the minimum by comparator | Any finite stream | min(comparator), not sorted().findFirst() |
unordered() is a permission slip. It tells the stream library that preserving encounter order is not required downstream. The simple option is to keep order because it is easier to reason about; it stops being enough when parallel work is spending time preserving an order your business rule does not need.
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- Advanced: Optional, nulls and match operations
- Short-circuiting and infinite streams
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal