Course contents

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.

PremiumOften askedLesson15 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: first shelf versus any shelf
  2. findFirst versus findAny under the hood
  3. Parallel streams: determinism has a price
  4. Advanced: Optional, nulls and match operations
  5. Short-circuiting and infinite streams
  6. How to say it in the interview

🔎 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)

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 than findAny()
  • Choose findAny() for parallel searches when any matching element is acceptable
  • Describe the parallel cost of findFirst() on ordered streams and when unordered() helps
  • Use Optional<T> results safely, including the NullPointerException trap for null elements
  • Reason about anyMatch, allMatch, noneMatch, limit and infinite streams
  • Avoid sorted().findFirst() when min() 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.

Fig. 1

findFirst waits for the earliest ordered match while findAny can accept whichever parallel worker finds a matching element first

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

Practise this topicStream basics: interview questions & scenarios
Further reading (6)