Back to Blog
Java

Java Stream allMatch: Usage, Short-Circuiting, and Edge Cases

java stream allmatch: Learn how Stream.allMatch works in Java: its short-circuit behavior, handling empty streams, predicate null checks, and practical performance con...

Stream APIShort-circuitingPredicatesJava 8Functional Programming
Java Stream allMatch concept: a magnifying glass over a stream of elements with a checkmark indicating all elements pass the predicate.

The java stream allmatch operation is a terminal method on the Stream interface that checks whether every element in the stream satisfies a given predicate. It returns a boolean and is commonly used for validation, permission checks, or data quality rules. While the syntax is simple, the runtime behavior has several details that affect correctness and performance.

Consider a basic example:

List<Integer> numbers = List.of(2, 4, 6, 8); boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); System.out.println(allEven); // true

The predicate is applied to each element until one returns false. At that point, the stream is consumed and the result is false without examining the remaining elements. This is the short-circuiting behavior of allMatch, and it is the most important characteristic to understand when using the method.

How allMatch Processes Elements

allMatch evaluates the predicate on each element in the stream order unless the stream is unordered. The evaluation stops as soon as a predicate returns false. If the stream is finite and every element passes, the method returns true. If the stream is infinite, allMatch may never terminate if the predicate does not find a false result early.

The method signature is:

boolean allMatch(Predicate<? super T> predicate)

The predicate is a functional interface that takes an element and returns a boolean. Because allMatch is a terminal operation, the stream is considered consumed after the call, and it cannot be reused.

Short-Circuiting and Its Effects

Short-circuiting means that the operation does not process the entire stream when the result is already determined. For allMatch, the result is determined as soon as one element fails the predicate. This can save significant computation when the stream is large or when the predicate is expensive.

For example, if you have a list of user objects and you want to verify that all users have a verified email, the check stops at the first unverified user:

boolean allVerified = users.stream() .allMatch(User::isEmailVerified);

If the first user is unverified, the remaining users are not processed. This behavior is especially useful when the stream is backed by an expensive source, such as a database cursor or a generated sequence.

However, short-circuiting does not guarantee that the predicate is evaluated on the minimum number of elements in all cases. For parallel streams, the behavior is different, as discussed later.

Empty Streams Return True

A common misconception is that allMatch on an empty stream returns false because there are no elements to match. In fact, the Java Stream API defines that allMatch returns true for an empty stream. This is a logical extension of the universal quantifier: the predicate holds for all elements when there are no elements.

Stream.empty().allMatch(x -> false); // returns true

This behavior is consistent with mathematical logic and with the allMatch contract in the Java documentation. It is important to account for this when writing validation logic. If you need to ensure that the stream is non-empty, you must check the count or use findAny separately.

Predicate Null Handling

If the predicate argument passed to allMatch is null, the method throws a NullPointerException immediately. This is a standard requirement of the Stream API; all functional parameters are required to be non-null. In practice, this means you should always ensure the predicate is not null, especially when the predicate comes from a variable or a method reference that might be null.

Stream.of(1, 2).allMatch(null); // throws NullPointerException

This is a fail-fast behavior that helps catch programming errors early. It does not depend on the stream contents.

Parallel Streams and allMatch

When you call allMatch on a parallel stream, the predicate is evaluated concurrently on multiple elements. The short-circuiting behavior is still present, but the order of evaluation is not deterministic. The operation will stop as soon as any thread finds a false result, but other threads may already be processing elements. This means that the predicate may be invoked on more elements than strictly necessary, and the number of extra evaluations depends on the thread scheduling and the stream's characteristics.

For example:

boolean allPositive = numbers.parallelStream().allMatch(n -> n > 0);

If the first element processed is negative, the result is false, but other threads might have already evaluated the predicate on several positive elements. This is an inherent trade-off of parallel processing. For most validation tasks, the overhead of parallelization is not worth it unless the stream is very large and the predicate is expensive and thread-safe.

Your predicate must be thread-safe when used on a parallel stream. If the predicate has shared mutable state, it can lead to race conditions and incorrect results.

Comparing allMatch, anyMatch, and noneMatch

The Stream API provides three matching operations that are often confused:

MethodReturns true whenShort-circuits on
allMatchEvery element satisfies the predicateFirst false
anyMatchAt least one element satisfies the predicateFirst true
noneMatchNo element satisfies the predicateFirst true

All three are terminal and short-circuiting. The choice depends on the condition you want to verify. For example, anyMatch is useful for checking if at least one item meets a criterion, while noneMatch checks that no item does. allMatch is stricter: it requires every element to pass.

It is also important to note that noneMatch is the logical negation of anyMatch, but not of allMatch. !allMatch means that not every element passes, which is equivalent to anyMatch on the negated predicate, but not the same as noneMatch.

Common Mistakes and How to Avoid Them

One frequent mistake is using allMatch on a stream that may be empty and expecting false. If your validation requires at least one element, check the stream's size or use findAny first. For example:

if (list.isEmpty()) { // handle empty case } else { boolean allValid = list.stream().allMatch(Validator::isValid); }

Another mistake is assuming that allMatch evaluates the predicate on all elements even after a failure. This is not true; short-circuiting prevents further evaluation. If you need to count how many elements fail, you must use filter and count instead.

Also, be cautious when using allMatch with a stream that has been modified by peek or other intermediate operations. The order of evaluation matters, and side effects in peek may not occur for elements after the short-circuit point.

Performance Considerations and When to Use allMatch

The main performance benefit of allMatch is its short-circuiting behavior. For large collections, it can avoid processing all elements when the predicate fails early. However, if the predicate is cheap and the stream is small, the overhead of stream creation and method calls may be comparable to a simple loop.

In a sequential stream, the predicate is evaluated in encounter order. If you expect most elements to pass and the failure is likely near the end, allMatch will process most of the stream. In such cases, consider whether an early-exit loop would be more explicit, but the difference is usually negligible.

For parallel streams, the potential performance gain depends on the number of elements and the cost of the predicate. If the predicate is CPU-intensive and the stream is large, parallelization can help, but the short-circuiting may not reduce work as effectively as in sequential mode. Always measure with realistic data before committing to parallel streams.

Another consideration is that allMatch is a terminal operation, so it forces the evaluation of all intermediate operations. If you have a chain of filter, map, and other lazy operations, they will be executed as the predicate is applied. This is the expected behavior, but it means that the entire pipeline is executed at that point.

Edge Case: Infinite Streams

If you call allMatch on an infinite stream, the operation will never complete if the predicate never returns false. For example:

Stream.generate(Math::random).allMatch(x -> x > 0.5);

This will eventually return false because there is a high chance of a value <= 0.5, but it is not guaranteed in a finite time. If the predicate is always true, the operation will run forever. In practice, you should only use allMatch on infinite streams if you are certain the predicate will fail at some point, and even then, you need to be careful about resource consumption.

A safer approach is to use limit to bound the stream before applying allMatch:

Stream.generate(Math::random) .limit(1000) .allMatch(x -> x > 0.5);

This ensures the operation terminates.

Using allMatch with Custom Objects and Method References

Method references are a clean way to use allMatch when the predicate is a simple boolean method on the object. For example, if you have a Product class with a method isInStock(), you can write:

boolean allInStock = products.stream().allMatch(Product::isInStock);

This is equivalent to p -> p.isInStock(). Method references improve readability and reduce boilerplate. However, if the predicate needs additional parameters, such as a threshold, you must use a lambda or a custom Predicate instance.

When the predicate involves multiple conditions, you can chain them with and() or use a compound lambda:

boolean allAvailable = products.stream() .allMatch(p -> p.isInStock() && p.getPrice() < 100);

Keep the predicate logic simple and free of side effects. A predicate that modifies state or depends on external mutable state can produce inconsistent results, especially in parallel streams.

Final Technical Note: allMatch and Stream Reusability

Once allMatch is called, the stream is consumed and cannot be reused. If you need to perform multiple checks on the same data, you must create a new stream from the source each time. This is a common source of errors when developers try to reuse a stream variable:

Stream<String> stream = list.stream(); boolean allNonEmpty = stream.allMatch(s -> !s.isEmpty()); boolean allLong = stream.allMatch(s -> s.length() > 3); // throws IllegalStateException

The second call fails because the stream has already been operated upon. Always create a fresh stream for each terminal operation. This is not a limitation specific to allMatch, but it is a common pitfall when chaining multiple checks.

java stream allmatch: Practical Usage and Code Examples | RYUSLOG DEV