Java 8 Streams: Definitive Guide to findFirst() and findAny()

Introduction

The findFirst() and findAny() methods are terminal operations (terminate and return results) of the Stream API. Yet, there is something special about them - they not only terminate a stream, but also short circuit it:

 List<String> people = List.of("John", "Janette", "Maria", "Chris");

Optional<String> person = people.stream()
                .filter(x -> x.length() > 4)
                .findFirst();
        
Optional<String> person2 = people.stream()
                .filter(x -> x.length() > 4)
                .parallel()
                .findAny();

person.ifPresent(System.out::println);
person2.ifPresent(System.out::println);
Janette
Chris

So, what's the difference between these two and how do you efficiently use them?

In this guide, we'll be doing a deep-dive and explore the findFirst() and findAny() methods in Java, as well as their applications and best practices.

Terminal and Short-Circuiting?

Another commonly used terminal operation is the forEach() method, yet it is still fundementally different, besides being a different operation.

To get a sense of why the findFirst() and findAny() operations differ from other terminal facilities like forEach(), assume that you have a stream with an infinite number of elements.

When you call forEach() on such a stream, the operation will traverse all the elements in that stream.

For an infinite number of elements, your forEach() call will take an infinite amount of time to finish processing.

Yet, the findFirst() and findAny() do not have to check all the elements in a stream and short-circuit as soon as they find an element they're searching for. So, if you call them from an infinite stream they will terminate that stream as soon as they find what you instructed them to.

That suggests that these two operations will always conclude in finite time.

Note: It's worth noting that they'll short-circuit intermediate operations, such as the filter() method during execution as there's simply no need to filter further if a match is found.

The findFirst() and findAny() operations are thus very necessary when you want to exit stream processing that could run endlessly. As an analogy, consider these two operations as similar to what you may do to kill a classic while or for loop whose recursion is infinite.

This guide will explore how these two operations work in detail. First, we'll start with their official definitions. Second, we'll apply them to simple use cases. Then, we'll interrogate their intricate differences.

Finally, we'll use these findings to determine how best to use them in more demanding use cases; especially those that demand careful code design to enhance processing speed.

findFirst() and findAny() Definitions

findFirst() and findAny() return values - they do not return instances of streams like how intermediate operations like forEach() or filter() do.

Yet, the values that findFirst() and findAny() return are always an Optional<T> type.

If you'd like to read more about Optionals, read our Guide to Optionals in Java 8.

An optional is a:

[...] container object which may or may not contain a non-null value.

Credit: Java 8 Documentation

That's all to say - the find operation of these returns a null-safe value, in case the value isn't present in the stream.

The findFirst() method returns the first element of a stream or an empty Optional. If the stream has no encounter order, any element is returned, as it's ambiguous which is the first one anyway.

The findAny() method returns any element of the stream - much like findFirst() with no encounter order.

Use Cases of findFirst() and findAny()

Let's take a look at some use cases of these methods and when you might prefer one over the other. Since examples with Strings typically don't get complex, say you have a stream of Person objects:

Stream<Person> people = Stream.of(
        new Person("Lailah", "Glass"),
        new Person("Juliette", "Cross"),
        new Person("Sawyer", "Bonilla"),
        new Person("Madilynn", "Villa"),
        new Person("Nia", "Nolan"),
        new Person("Chace", "Simmons"),
        new Person("Ari", "Patrick"),
        new Person("Luz", "Gallegos"),
        new Person("Odin", "Buckley"),
        new Person("Paisley", "Chen")
);

Where a Person is:

public class Person implements Comparable<Person> {

    private final String firstName;
    private final String lastName;

    // Constructor, getters
    // equals() and hashCode()
    // compareTo(Person otherPerson)

    @Override
    public String toString() {
        return String.format("Person named: %s %s", firstName, lastName);
    }
    
    @Override 
    public int compareTo(Person otherPerson) {        
        return Comparator.comparing(Person::getFirstName)
                .thenComparing(Person::getLastName)
                .compare(this, otherPerson);
    }
}

The comparator compares people using their firstName fields, and then by their lastName fields.

And, you want to know which person has a fairly long first name. That being said - you may want to find a person with a long name, or the first person with a long name.

Let's say that any name with more than 7 letters is a long name:

private static boolean isFirstNameLong(Person person) {
    return person.getFirstName().length() > 7;
}

Using the Person stream, let's filter the objects using the isFirstNameLong() predicate and find a person:

people
    .filter(FindTests::isFirstNameLong) // (1)
    .findFirst() // (2)
    .ifPresentOrElse( // (3)
            System.out::println, // (3.1)
            () -> System.out.println("No person was found") // (3.2)
    );

The first line filters the stream of people and a returns a new stream that contains only the Person objects whose firstName has more than seven letters.

If you'd like to read more about the filter() method, read our Java 8 Streams: Guide to the filter() Method.

The second line terminates the stream if the findFirst() operation finds a firstName with more than seven letters.

The third line interrogates the Optional<Person> that the findFirst() operations returns. Whereby, it may (or, may not) contain a Person with a long first name:

  1. If the Optional contains a Person with a long firstName, print its details to console.
  2. If not, print a message: "No person was found."

Hence, when you run the code above, you will get the output:

Person named: Juliette Cross

Now, let us try to implement this use case with the findAny() operation instead. This is as easy as just switching the findFirst() call above with findAny():

people
    .filter(FindTests::isFirstNameLong) // (1)
    .findAny() // (2)
    .ifPresentOrElse( // (3)
            System.out::println, // (3.1)
            () -> System.out.println("No person was found") // (3.2)
    );

Yet, when we run the code, we get the same output, even if you run the code multiple times:

Person named: Juliette Cross

What gives?

Well, both of these short-circuit the filter() operation as soon as the Person with the name "Juliette Cross" is encountered, so the same result is returned. The findAny() method doesn't get to choose between her and other people, as nobody after her even gets admitted into the stream.

This result indicates that we are not exploiting the capabilities of both findFirst() and findAny() fully with this setup. Let's take a look at how we can change the environment of these methods to retrieve the results we were expecting.

Choosing Between findFirst() and findAny()

The inclusion of the term "first" in the findFirst() operation implies that there is a particular order of elements and you are only interested in the element that is in the first position.

As implied before - these methods are the same depending on whether you initiate your stream with encounter order or not.

Both act like findAny() if there's no order, and both act like findFirst() if there is order.

So, let us revisit the use case to improve the approach to designing the solution. We needed to find a Person with a lengthy firstName; one which has more than seven letters.

Thus, we should elaborate our requirement further to seek for not only a lengthy firstName, but a name that comes first too when those lengthy first names are in a sorted order.

That way, we would change the code to read as:

people.sorted() //(1)
     .peek(person -> System.out.printf("Traversing stream with %s\n", person)) //(2)
     .filter(FindTests::isFirstNameLong) //(3)
     .findFirst() //(4)
     .ifPresentOrElse( //(5)
         System.out::println, //(5.1)
         () -> System.out.println("No person was found") //(5.2)
 );
Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

With this code snippet, we have added two more steps compared with the earlier snippet.

First, we sort the Person objects using their natural order. Remember, the Person class implements the Comparable interface. Thus, you should specify how Person objects should be sorted as you implement Comparable.

If you'd like to read more about sorting with Streams, read our Java 8 – How to Use Stream.sorted()

Then, we peek() into the stream to get a glimpse of what the operations are doing to the stream, followed by filtering using our predicate that only accepts Person objects whose firstName fields have more than seven letters.

Finally, we call findFirst() and handle the Optional result of the findFirst() operation.

When we examine what the use of sorted() did to our stream manipulation earlier we get the following outputs.

After calling peek():

Traversing stream with Person named: Ari Patrick
Traversing stream with Person named: Chace Simmons
Traversing stream with Person named: Juliette Cross

After interrogating the Optional that findFirst() returned:

Person named: Juliette Cross

The eventual result of our findFirst() call is similar to the other two previous attempts, as we're traversing the same list with the same order.

Yet, something is starting to make a bit more sense about the findFirst() operation. It returned the first Person object that had a lengthy firstName when those objects were sorted in an ascending alphabetical order.

To illustrate that aspect further, let's return the first Person object with a lengthy firstName when the alphabetical sort is in reverse.

Instead of calling a plain sorted() operation on the people stream, let us use a sort operation that takes a custom Comparator function:

people.sorted(Comparator.comparing(Person::getFirstName).reversed()) //(1)
         .peek(person -> System.out.printf("Traversing stream with %s\n", person))//(2)
         .filter(x -> x.getFirstName().length() > 7)//(3)
         .findFirst()//(4)
         .ifPresentOrElse(//(5)
             System.out::println,//(5.1)
             () -> System.out.println("No person was found")//(5.2)
);

We supply a Comparator that is similar to the one the Person class provides. The only differences are that the one we have implemented above uses only the firstName field for comparison. Then it changes the sort order to arrange the names in reverse alphabetical order - via the reversed() operation in the Comparator call.

Using the custom sort operation, we get the following outputs.

After calling peek():

Traversing stream with Person named: Sawyer Bonilla
Traversing stream with Person named: Paisley Chen
Traversing stream with Person named: Odin Buckley
Traversing stream with Person named: Nia Nolan
Traversing stream with Person named: Madilynn Villa

After interrogating the Optional that findFirst() returned:

Person named: Madilynn Villa

So, there you have it. Our latest use of findFirst() serves our updated use case adequately. It found the first Person with a lengthy firstName from a selection of several possibilities.

When to use findAny()?

There are instances where you have a stream, but you only want to select a random element; as long as it meets certain conditions and the operation itself takes the shortest time possible.

Thus, given our ongoing use case, you may only want to retrieve a Person object who has a lengthy firstName. It may also not matter whether that person's name comes first in an alphabetical order or last. You simply just want to find anyone who has a long first name.

This is where findAny() works best.

Yet, with a plain attempt (such as the following) you may not see any difference between findFirst() and findAny():

people.peek(person -> System.out.printf("Traversing stream with %s\n", person))
        .filter(FindTests::isFirstNameLong)
        .findAny()
        .ifPresentOrElse(
                System.out::println,
                () -> System.out.println("No person was found")
        );

The output from the peek() operation, for example, returns this:

Traversing stream with Person named: Lailah Glass
Traversing stream with Person named: Juliette Cross

And the output after findAny() returns:

Person named: Juliette Cross

This means that our findAny() operation simply traversed the stream in a sequential manner. Then, it picked the first Person object whose firstName has more than seven letters.

There is nothing special that it did that findFirst() could not have done, in short.

Yet, when you parallelize the stream, you will start noticing a few changes to the way findAny() works. So, in the previous code, we could add a simple call to the parallel() operation on the stream:

people.peek(person -> System.out.printf("Traversing stream with %s\n", person))
        .parallel()
        .filter(FindTests::isFirstNameLong)
        .findAny()
        .ifPresentOrElse(
                System.out::println,
                () -> System.out.println("No person was found")
        );

And when you run the code, you may get a peek() output such as:

Traversing stream with Person named: Ari Patrick
Traversing stream with Person named: Juliette Cross
Traversing stream with Person named: Sawyer Bonilla
Traversing stream with Person named: Odin Buckley
Traversing stream with Person named: Chace Simmons

With an eventual findAny() output of:

Person named: Juliette Cross

True, the output of this findAny() matches the previous one due to sheer chance. But, did you notice that the stream in this case checked more elements? And, the encounter order was not sequential?

Also, if we run the code again, you may get another output like this one after peek():

Traversing stream with Person named: Ari Patrick
Traversing stream with Person named: Chace Simmons
Traversing stream with Person named: Sawyer Bonilla
Traversing stream with Person named: Odin Buckley
Traversing stream with Person named: Luz Gallegos
Traversing stream with Person named: Paisley Chen
Traversing stream with Person named: Nia Nolan
Traversing stream with Person named: Madilynn Villa
Traversing stream with Person named: Juliette Cross
Traversing stream with Person named: Lailah Glass

And here, the findAny() output is:

Person named: Madilynn Villa

It is thus now self-evident how findAny() works. It selects any element from a stream without regard for any encounter order.

If you were dealing with a very huge number of elements, then this is actually a good thing. It means that your code may conclude operating sooner than when you would check elements in a sequential order, for example.

Conclusion

As we have seen, the findFirst() and findAny() operations are short-circuiting terminal operations of the Stream API. They may terminate a stream even before you get to traverse the entirety of it with other intermediate operations (such as, filter()).

This behavior is very important when you are handling a stream which has very many elements. Or, a stream that has an endless number of elements.

Without such a capability, it means your stream operations may run infinitely; hence, causing errors like StackOverflowError. Again, think of this findFirst() and firstAny() short-circuiting behavior as one that addresses the dreaded bugs associated with poorly designed for and while loops that recurse endlessly.

Otherwise, keep in mind that findFirst() and findAny() are well suited to different use cases.

When you have a stream of elements whose encounter order is known beforehand, prefer the findFirst() operation. But, in a case where parallelization is needed and you do not care which particular element you need to select, go for findAny().

Be careful to not take the phrase "do not care which element you select" out of context, though. The phrase implies that out of a stream of elements, a few meet the conditions that you have set out. Yet, you aim to select any element out of those few that meet your requirements.

The code used in the article is available on GitHub.

Last Updated: November 28th, 2021
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Hiram KamauAuthor

In addition to catching code errors and going through debugging hell, I also obsess over whether writing in an active voice is truly better than doing it in passive.

Make Clarity from Data - Quickly Learn Data Visualization with Python

Learn the landscape of Data Visualization tools in Python - work with Seaborn, Plotly, and Bokeh, and excel in Matplotlib!

From simple plot types to ridge plots, surface plots and spectrograms - understand your data and learn to draw conclusions from it.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms