Chaining Multiple Java Predicates Together

A guide to combining multiple Predicate instances to represent complex filter conditions in Java.

Overview

Java Predicates represent a boolean-valued function that takes an argument and returns a boolean value. Predicates represent a condition that applies to the given value. In Java, Predicates are useful while filtering elements of a Stream or a Collection.

This article provides examples of joining or chaining multiple Predicates to form complex logical conditions inside filter() methods.

Java Simple Predicate Example

Java Stream’s filter(Predicate)‘ method applies the given Predicate to each element of the Stream and returns a new Stream of the matching elements.

Example of using a simple filter with a Predicate.

List<String> names = List.of("Maria", "Sandra", "Rachel", "Mary", "Michael");
List<String> output = names.stream()
    .filter(n -> n.startsWith("M"))
    .toList();
output.forEach(System.out::println);
//prints:
//Maria
//Mary
//MichaelCode language: Java (java)

From a List of names, we selected only those that start with ‘M’.

Multiple Filters Examples

The last example contained a single Predicate. Let’s add one more Predicate to our filter condition. Thanks to the flexibility offered by Java Stream’s API, we can chain two filter() methods to apply two Predicates.

Example of chaining multiple filter() methods to apply multiple Predicates.

List<String> names = List.of("Maria", "Sandra", "Rachel", "Mary", "Michael");
List<String> output = names.stream()
    .filter(n -> n.startsWith("M"))
    .filter(n -> n.length() > 4)
    .toList();
output.forEach(System.out::println);
//prints:
//Maria
//MichaelCode language: Java (java)

This time, we selected those names that start with ‘M’ and are longer than four characters.

Single Filter with Multiple Conditions

Instead of chaining multiple filter() methods, we can apply multiple filter conditions using a complex Lambda Expression.

Example of using a single filter() method with multiple filter conditions.

List<String> names = List.of("Maria", "Sandra", "Rachel", "Mary", "Michael");
List<String> output = names.stream()
    .filter(n -> n.startsWith("M") && n.length() > 4)
    .toList();
output.forEach(System.out::println);
//prints:
//Maria
//MichaelCode language: Java (java)

Like the previous example, we select names that start with the character ‘M’ and are longer than four characters. However, instead of using a filter() chaining, we used a complex Predicate as a Lambda Expression.

Combining Multiple Predicates – Predicate Chaining

We studied examples of applying simple and more complex Predicates in a filter method. The Predicate.and(), Predicate.or(), and Predicate.negate() methods provide a much cleaner, more readable, and flexible way of combining multiple Predicate conditions to represent very complex logical conditions.

Let’s study how to combine multiple Predicates using the logical and(), or(), and negate() methods from the Predicate interface.

Predicate.and() – Logical AND

The ‘and(Predicate)‘ method combines the given Predicate with this and returns a composed Predicate. The resulting combined Predicate is a short-circuiting logical AND operation where the second Predicate will not be evaluated when the first is False.

Let’s rewrite the previously seen example to combine two Predicates with the and() method.

Example of chaining multiple Predicates together using the and() method.

Predicate<String> predicate1 = n -> n.startsWith("M");
Predicate<String> predicate2 = n -> n.length() > 4;

List<String> names = List.of("Maria", "Sandra", "Rachel", "Mary", "Michael");
List<String> output = names.stream()
    .filter(predicate1.and(predicate2))
    .toList();
output.forEach(System.out::println);
//prints:
//Maria
//Michael
Code language: Java (java)

In this example, we combined two pre-defined Predicates using the and() method to have the same effect of using two separate filter() methods or writing a more complex Lambda Expression in a single filter().

Predicate.or() – Logical OR

The ‘or(Predicate)‘ method combines the given Predicate with this and returns a composed Predicate. The resulting combined Predicate is a short-circuiting logical OR operation where the second Predicate will not be evaluated when the first is True.

Example of chaining multiple Predicates together using the or() method.

Predicate<String> predicate1 = n -> n.startsWith("M");
Predicate<String> predicate2 = n -> n.length() > 4;

List<String> names = List.of("Maria", "Sandra", "Rachel", "Mary", "Michael");
List<String> output = names.stream()
    .filter(predicate1.or(predicate2))
    .toList();
output.forEach(System.out::println);
//prints:
//Maria
//Sandra
//Rachel
//Mary
//Michael
Code language: Java (java)

We used the or() method to chain two Predicates in a filter() method to select those names that either start with the character ‘M’ or are longer than four characters.

Predicate.negate() – Logical NOT

The negate()‘ method returns a Predicate that reverses the condition of this Predicate. We can use the negate(), and(), and or() methods to derive more complex combinations of Predicate conditions.

Example of chaining multiple Predicates together using the and() and negate() method.

Predicate<String> predicate1 = n -> n.startsWith("M");
Predicate<String> predicate2 = n -> n.length() > 4;

List<String> names = List.of("Maria", "Sandra", "Rachel", "Mary", "Michael");
List<String> output = names.stream()
    .filter(predicate1.negate().or(predicate2.negate()))
    .toList();
output.forEach(System.out::println);
//prints:
//Sandra
//Rachel
//Mary
Code language: Java (java)

This time we selected those names that either do not start with the character ‘M’ or are not longer than four characters.

Combining a Collection of Predicates

Let’s see an example of chaining a Collection of Predicates using the Stream reduce() method and a logical operator.

List<Predicate<String>> predicates = List.of(
    n -> n.startsWith("M"),
    n -> n.length() > 4
);

List<String> names = List.of("Maria", "Sandra", "Rachel", "Mary", "Michael");
List<String> output = names.stream()
    .filter(predicates.stream()
        .reduce(p -> true, Predicate::and))
    .toList();
output.forEach(System.out::println);
//prints
//Maria
//MichaelCode language: Java (java)

Alternatively, we can combine a collection of Predicates reducing them by logical OR operation.

List<Predicate<String>> predicates = List.of(
    n -> n.startsWith("M"),
    n -> n.length() > 4
);

List<String> names = List.of("Maria", "Sandra", "Rachel", "Mary", "Michael");
List<String> output = names.stream()
    .filter(predicates.stream()
        .reduce(p -> false, Predicate::or))
    .toList();
output.forEach(System.out::println);
//prints
//Maria
//Sandra
//Rachel
//Mary
//MichaelCode language: Java (java)

Summary

This article explained different ways of combining or chaining multiple Predicates in Java. In the first section, we saw examples of chaining the filter() method to apply multiple Predictes or using a complex Lambda expression in a single filter() method.

In the next section, we understood that the Predicate interface provides methods like and(), or(), and negate() that we can use to logically combine multiple Predicates to derive complex filter() operations. Lastly, we saw examples of combining a collection of Predicates and reducing them using AND or OR operators.

The complete source of the examples used is available at our GitHub Repository.