Java Stream findFirst() vs findAny(): Differences and Examples

Java Stream findFirst vs findAny explained. findFirst() keeps encounter order, while findAny() may return any element in parallel streams. With null rules.

A parallel stream splits four cities into two chunks; findAny can return a match from whichever chunk finishes first, while findFirst always returns Oslo from the leftmost chunk

The Java Stream methods findFirst() and findAny() both return an Optional with one element of the stream, but findFirst() always returns the first element in encounter order, whereas findAny() may return any element. On an empty stream, both return an empty Optional.

We call them after filter() to get one matching element, for example the first unpaid invoice in a sorted list or any free server from a pool. The difference shows up in parallel streams, where findAny() can return whichever element a thread finds first.

The following example runs both methods on the same parallel stream.

List<String> cities = List.of("Oslo", "Lima", "Rome", "Kyiv");
Optional<String> first = cities.parallelStream().findFirst();                                 // Optional[Oslo], on every run
Optional<String> any = cities.parallelStream().findAny();                                     // one of the four cities, can change between runs
Optional<String> firstWithR = cities.stream().filter(c -> c.startsWith("R")).findFirst();     // Optional[Rome]

Notice that the parallel findFirst() still returns Oslo. We look at both methods in detail, at sources without an order, at the NullPointerException for null elements, and at when findAny() is worth choosing.

1. Stream findFirst() vs findAny() Compared

A stream has an encounter order when its source has one. A List, an array, Stream.of() and a TreeSet are ordered, while a HashSet is not. The method findFirst() respects that order, so in a parallel stream it has to wait until it knows which element comes first. The method findAny() is free to return the first element any thread finds.

A parallel stream splits four cities into two chunks; findAny can return a match from whichever chunk finishes first, while findFirst always returns Oslo from the leftmost chunk
In a parallel stream, findFirst() waits for the leftmost chunk, while findAny() can take a match from any chunk

We use findFirst() whenever the order of the elements matters, and findAny() only when any matching element is equally good. In a sequential stream, both return the first element on current JDKs, but only findFirst() promises it.

FeaturefindFirst()findAny()
Result on an ordered streamThe first element in encounter orderAny element, not specified
Result in a parallel streamSame as sequential, stable across runsCan differ between runs
Result on an unordered source such as HashSetAny elementAny element
Empty streamEmpty OptionalEmpty Optional
Selected element is nullNullPointerExceptionNullPointerException
Parallel costHas to coordinate threads to keep the orderCan stop at the first match of any thread
Typical useEarliest record, first match in a sorted listAny free resource, existence plus the element

2. Getting the First Element With findFirst()

The method findFirst() is a short-circuiting terminal operation. It returns an Optional describing the first element, or an empty Optional if the stream is empty. Combined with filter(), it returns the first element that matches a condition and stops reading the stream there.

List<String> cities = List.of("Oslo", "Lima", "Rome", "Kyiv");
Optional<String> head = cities.stream().findFirst();                                       // Optional[Oslo]
Optional<String> firstEndingA = cities.stream().filter(c -> c.endsWith("a")).findFirst();  // Optional[Lima]
Optional<String> firstLong = cities.stream().filter(c -> c.length() > 4).findFirst();      // Optional.empty

The Optional forces us to decide what happens when nothing matches. We take a default with orElse(), throw with orElseThrow(), or run code for both cases with ifPresentOrElse(), instead of calling get() without a check.

List<String> cities = List.of("Oslo", "Lima", "Rome", "Kyiv");
String name = cities.stream().filter(c -> c.length() > 4).findFirst().orElse("none");    // "none"
String rome = cities.stream().filter(c -> c.startsWith("R")).findFirst().orElseThrow();   // "Rome"
String zurich = cities.stream().filter(c -> c.startsWith("Z")).findFirst().orElseThrow(); // NoSuchElementException: No value present

Making the stream parallel does not change the result of findFirst(). The call cities.parallelStream().filter(c -> c.endsWith(“a”)).findFirst() returns Optional[Lima] on every run, because the order is part of the contract.

2.1. findFirst() on a HashSet or Another Unordered Source

If the source has no encounter order, “first” has no meaning, and findFirst() is allowed to return any element. A HashSet iterates in hash order, which depends on the elements and the capacity, so the result can change when the data changes. The same applies after unordered(). To get a predictable result, we sort the stream first or use a sorted collection.

List<String> cities = List.of("Oslo", "Lima", "Rome", "Kyiv");
Set<String> citySet = new HashSet<>(cities);
Optional<String> fromSet = citySet.stream().findFirst();                 // some city, depends on hash order
Optional<String> alphabetical = citySet.stream().sorted().findFirst();   // Optional[Kyiv]
String treeFirst = new TreeSet<>(cities).first();                        // "Kyiv"

When we only need the smallest element, min(Comparator.naturalOrder()) returns the same Optional[Kyiv] without sorting the whole stream.

2.2. findFirst() Compared With getFirst() and get(0)

For a whole list without a filter, a stream is not needed. Since Java 21, every sequenced collection has getFirst(), which throws a NoSuchElementException on an empty list, while get(0) throws an IndexOutOfBoundsException. The stream version returns an empty Optional instead of throwing.

List<String> cities = List.of("Oslo", "Lima", "Rome", "Kyiv");
String head = cities.getFirst();                                // "Oslo"
List<String> none = List.of();
Optional<String> safeHead = none.stream().findFirst();         // Optional.empty
String noHead = none.getFirst();                                // NoSuchElementException

3. Getting Any Element With findAny()

The method findAny() returns an Optional describing some element of the stream. Its behavior is explicitly nondeterministic, which allows maximal performance in parallel operations, at the cost that two calls on the same source may return different elements.

List<String> cities = List.of("Oslo", "Lima", "Rome", "Kyiv");
Optional<String> seqAny = cities.stream().findAny();                                      // Optional[Oslo] on JDK 25, not guaranteed
Optional<String> parAny = cities.parallelStream().filter(c -> c.length() == 4).findAny();  // any of the four cities
Optional<String> nothing = Stream.<String>empty().findAny();                              // Optional.empty

In a sequential stream, the current JDK returns the first element, but code must not depend on it, and a unit test that asserts “Oslo” for findAny() tests unspecified behavior. In a parallel stream, the result depends on how the threads split the work, so we only use findAny() where every matching element is an acceptable answer.

Typical uses are picking any idle worker or any sample row that shows the user that data exists. If we only need to know whether a match exists, anyMatch() from the anyMatch(), allMatch() and noneMatch() guide returns a boolean and says what we mean.

4. NullPointerException When the Selected Element Is null

An Optional cannot hold null, so both methods throw a NullPointerException if the element they select is null. A list from a database mapper or a JSON parser can contain null entries, and the exception appears later in the find call, not where the null was added.

List<String> names = Arrays.asList(null, "Lima", "Rome");
Optional<String> boom = names.stream().findFirst();                                // NullPointerException
Optional<String> safe = names.stream().filter(Objects::nonNull).findFirst();      // Optional[Lima]

Filtering with Objects::nonNull before the find step skips the null entries. If a null first element carries meaning, we read it with names.getFirst() instead, which returns null without throwing.

5. When findAny() Is Faster

In a sequential stream, both methods do the same work and read elements until the first one passes the filter. In a parallel stream, the work is split into chunks that threads process at the same time. The method findAny() can stop as soon as any chunk has a match, whereas findFirst() has to wait for the chunks to the left of that match, because one of them might contain an earlier match.

The gain only matters when the stream is large, the filter is expensive and matches are spread across the data. For a list of a few hundred elements, a sequential stream with findFirst() is the better default, and the parallelism overhead is larger than any saving.

6. Real-World Example With a Support Desk

A support desk app assigns each new ticket to an agent who has no open tickets. Any idle agent is fine, so findAny() fits, while the report of the longest-waiting ticket needs a defined order. Each agent is a record with a name and the number of open tickets.

record Agent(String name, int openTickets) {}
record Ticket(int id, int minutesWaiting) {}
List<Agent> agents = List.of(new Agent("Ana", 3), new Agent("Ben", 0), new Agent("Cleo", 0));
Optional<Agent> idle = agents.parallelStream().filter(a -> a.openTickets() == 0).findAny();   // Ben or Cleo
String assignee = idle.map(Agent::name).orElse("queue");                                    // "Ben" or "Cleo"
List<Ticket> tickets = List.of(new Ticket(7, 12), new Ticket(8, 45), new Ticket(9, 30));
Optional<Ticket> oldest = tickets.stream().max(Comparator.comparingInt(Ticket::minutesWaiting));   // Optional[Ticket[id=8, minutesWaiting=45]]
Optional<Ticket> firstOverLimit = tickets.stream().filter(t -> t.minutesWaiting() > 20).findFirst();   // Optional[Ticket[id=8, minutesWaiting=45]]

The assignment works whichever idle agent comes back, and the fallback “queue” covers the case where everyone is busy. For the oldest ticket, we use max() instead of sorting and taking the first element, because max() reads the list once without building a sorted copy.

7. findFirst() and findAny() FAQs

Readers comparing the two methods also ask about sequential streams, filtering, the Optional return type and old Java versions.

7.1. Does findAny() always return the first element in a sequential stream?

No, it is not guaranteed. On current JDKs, a sequential findAny() returns the first element, but the contract allows any element. Code that needs the first element calls findFirst().

7.2. Is findAny() faster than findFirst()?

Only in parallel streams, and only when the data is large enough. In sequential streams, both read the same elements, as explained in section 5.

7.3. How do we get the first element that matches a condition?

We call filter() with the condition and findFirst() after it, for example cities.stream().filter(c -> c.startsWith(“R”)).findFirst(). The stream stops at the first match, so the rest of the list is never tested.

7.4. Why do findFirst() and findAny() return an Optional?

Because the stream can be empty, or the filter can remove every element. The Optional makes the empty case visible in the method signature, so we handle it with orElse(), orElseThrow() or ifPresent() instead of checking for null.

7.5. Can findFirst() be used on an infinite stream?

Yes. Both methods are short-circuiting, so they return as soon as an element passes the filter. For example, Stream.iterate(1, n -> n * 3).filter(n -> n > 100).findFirst() returns Optional[243]. If no element ever matches, the call never returns.

8. Conclusion

Both findFirst() and findAny() return one element wrapped in an Optional and stop reading the stream early. The method findFirst() keeps the encounter order, even in parallel streams, so it is the default for lists, sorted data and anything a user sees.

The method findAny() trades that guarantee for less coordination in parallel streams, so we use it when every match is acceptable. In both cases, a null element throws a NullPointerException, and an unordered source such as a HashSet has no real first element.

9. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. Can I assume that for findFirst() method, the output will not differ irrespective of whether the stream is parallel stream or sequential stream?

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.