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()
andfindAny()
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 String
s 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:
- If the
Optional
contains aPerson
with a longfirstName
, print its details to console. - 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 thePerson
with the name "Juliette Cross" is encountered, so the same result is returned. ThefindAny()
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 likefindFirst()
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)
);
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.