What do findFirst() and findAny() operations do
Both findFirst() and findAny() are terminal short-circuiting operations that search for an element matching upstream pipeline criteria and return the result wrapped inside an Op...
🟢 Junior Level
30-Second Summary
Both findFirst() and findAny() are terminal short-circuiting operations that search for an element matching upstream pipeline criteria and return the result wrapped inside an Optional<T> (to safely model situations where no matching element exists or the stream is empty):
findFirst(): Guarantees returning the strictly first element according to the source data’s traversal order (Encounter Order).findAny(): Returns any matching element. In a single-threaded sequential stream, it almost always returns the first element; in a multi-threadedparallelStream(), it returns the element from whicheverForkJoinPoolworker thread finishes its evaluation first.
Java 21 Code Examples
import java.util.List;
import java.util.Optional;
public class FindFirstFindAnyDemo {
public static void main(String[] args) {
List<String> names = List.of("Ivan", "Alex", "Anna", "Boris");
// 1. findFirst: strictly the first name starting with "A"
Optional<String> firstA = names.stream()
.filter(name -> name.startsWith("A"))
.findFirst();
System.out.println("First 'A': " + firstA.orElse("Not found")); // Alex
// 2. findAny: any name starting with "A" (in sequential stream, typically Alex)
Optional<String> anyA = names.stream()
.filter(name -> name.startsWith("A"))
.findAny();
System.out.println("Any 'A': " + anyA.orElse("Not found")); // Alex
// 3. Handling an empty match safely
Optional<String> notFound = names.stream()
.filter(name -> name.startsWith("Z"))
.findFirst();
System.out.println("Search empty: " + notFound.isEmpty()); // true
}
}
🟡 Middle Level
The Role of Encounter Order (Spliterator.ORDERED)
The exact behavior of findFirst() and findAny() depends on whether the stream source enforces an encounter order:
| Collection Source | Encounter Order | findFirst() Behavior |
findAny() in parallelStream() |
|---|---|---|---|
ArrayList, arrays, LinkedHashSet |
Strictly Ordered | Always returns the element with lowest index | Returns whichever thread completes first |
HashSet, HashMap.keySet() |
Unordered | First element in current hash bucket traversal | Any element returned by first worker thread |
Choosing the Right Terminal Operation
Developers frequently confuse when to use anyMatch(), findFirst(), and limit(1):
// 1. Checking presence only (boolean):
// ✅ OPTIMAL: anyMatch() uses zero heap allocations and creates no Optional
boolean exists = users.stream().anyMatch(User::isAdmin);
// 2. Retrieving an actual element instance:
// ✅ OPTIMAL: findFirst() or findAny() returns Optional<User>
Optional<User> admin = users.stream().filter(User::isAdmin).findFirst();
// 3. Continuing pipeline operations on a single matched element:
// ✅ OPTIMAL: limit(1) preserves Stream<User> downstream
List<User> singleList = users.stream().filter(User::isAdmin).limit(1).toList();
🔴 Senior Level
Under the Hood: FindTask in ForkJoinPool
In OpenJDK (java.util.stream.FindOps), parallel searching is coordinated via FindTask:
Parallel Execution with 4 ForkJoin Worker Threads:
Worker 1 [Slice 0..250k]: Evaluating...
Worker 2 [Slice 250k..500k]: Evaluating...
Worker 3 [Slice 500k..750k]: FOUND MATCH! ──► findAny(): RETURNS IMMEDIATELY & cancels Workers 1, 2, 4!
──► findFirst(): BLOCKS & WAITS for Workers 1 & 2!
Worker 4 [Slice 750k..1000k]: Evaluating...
findAny()(Maximum Parallel Throughput):- The moment Worker 3 discovers a match, it triggers an atomic
shortCircuit(result). - It invokes
cancelLaterNodes(), signaling all sibling subtasks in theForkJoinPoolqueue to abort immediately, returning the result without inter-thread delays.
- The moment Worker 3 discovers a match, it triggers an atomic
findFirst()(Encounter Order Contention):- Must strictly guarantee encounter order.
- Even if Worker 3 finds a match instantly, it cannot return until Worker 1 and Worker 2 complete their scans to confirm that no earlier match exists in the left sub-arrays.
- This results in thread stalls, inter-thread synchronization overhead, and reduced parallel scaling.
The NullPointerException Trap in findFirst() and findAny()
Inside OpenJDK’s FindOp, matching elements are wrapped using Optional.of(result). Under the Java specification, an Optional cannot contain null:
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
public class FindNullTrapDemo {
public static void main(String[] args) {
List<String> list = Arrays.asList("A", null, "B");
try {
// ❌ RUNTIME DISASTER: Throws NullPointerException!
Optional<String> result = list.stream()
.filter(Objects::isNull)
.findFirst();
} catch (NullPointerException e) {
System.err.println("Crashed with NPE: Optional cannot hold a null element!");
}
}
}
If your dataset may legitimately contain null elements, never use filter(Objects::isNull).findFirst(); use anyMatch(Objects::isNull) instead.
Primitive Specialized Streams: Zero-Allocation Searching
To avoid object instantiation and boxing overhead when searching numeric collections in high-throughput applications:
IntStream.findFirst()$\rightarrow$ returnsOptionalIntLongStream.findFirst()$\rightarrow$ returnsOptionalLongDoubleStream.findFirst()$\rightarrow$ returnsOptionalDouble
These primitive optionals store values in unboxed primitive fields, eliminating heap memory allocations and garbage collector pressure.
4 Tricky Questions
1. Why does findAny() achieve significantly higher throughput than findFirst() in a multi-threaded parallelStream()?
Answer:
findFirst() is constrained to preserve the source collection’s encounter order. If a worker thread searching elements in the middle or tail of the dataset finds a match, it must suspend and wait for all preceding worker threads to finish processing upstream partitions to verify that no earlier element matched.
In contrast, findAny() is completely decoupled from encounter order. Whichever thread in the ForkJoinPool finds a match first immediately invokes cancelLaterNodes() to abort sibling tasks and returns its value, avoiding thread blocking and pipeline synchronization.
2. What happens if a stream contains a matching null element and findFirst() is invoked?
Answer:
The invocation throws a NullPointerException.
Under the hood, OpenJDK’s FindOp wraps the terminal match via Optional.of(result). Because Optional.of() explicitly disallows null arguments (Objects.requireNonNull(value)), finding a null value causes an immediate NullPointerException rather than returning an empty Optional.
3. Can findFirst() and findAny() return different elements in a single-threaded sequential stream?
Answer:
- On ordered sources (such as
ArrayList, linked lists, or arrays), both methods traverse elements sequentially and return the identical first matching element. - On unordered sources (such as
HashSetorHashMap.keySet()), the JVM specification does not requirefindAny()to select the first encountered element. While HotSpot usually returns the first iterated bucket element in sequential streams,findAny()explicitly reserves the right to return any matching element.
4. How do primitive stream specializations (IntStream) optimize findFirst() and findAny() operations?
Answer:
IntStream, LongStream, and DoubleStream define specialized versions of findFirst() and findAny() that return OptionalInt, OptionalLong, and OptionalDouble instead of Optional<T>.
These primitive optionals encapsulate the result using primitive scalar fields (such as int value and boolean isPresent) without boxing primitives into wrapper objects (Integer). This eliminates heap object allocations, preserves CPU cache locality, and minimizes garbage collection churn.
🎯 Interview Cheat Sheet
findFirst() vs. findAny() Comparative Matrix
| Dimension | findFirst() | findAny() |
| :— | :— | :— |
| Return Type | Optional<T> | Optional<T> |
| Encounter Order | Strictly Guaranteed | Not Guaranteed |
| Sequential Stream | First matching element | First matching element (typically) |
| parallelStream() Performance| Slower (Waits for earlier chunks) | Fastest (Immediate abort) |
| Behavior on null Match | Throws NullPointerException | Throws NullPointerException |
| Return on Empty Stream | Optional.empty() | Optional.empty() |
Key Takeaways
- Encounter Order: Use
findFirst()when order matters; usefindAny()in parallel streams for performance. - Null Hazard: If a matching element is
null, both methods throwNullPointerException. - Short-Circuiting: Traversal stops as soon as a match is validated.
- Presence Check: Use
anyMatch()instead offindFirst().isPresent()to avoidOptionalallocation.
Red Flags & Anti-Patterns to Avoid
- ❌ Using
findFirst()inparallelStream()when ordering is irrelevant: Degrades multi-threaded throughput. - ❌ Calling
.get()directly onfindFirst()without checkingisPresent()or usingorElse(): ThrowsNoSuchElementException. - ❌ Searching for
nullviafilter(Objects::isNull).findFirst(): Crashes withNullPointerException. - ❌ Using
findFirst().isPresent()instead ofanyMatch(): Incurs avoidableOptionalheap allocation.