Java 8 Streams - Filtering with Multiple Conditions

1. Overview

Java 8 Streams provide the filter() method to process and filter data based on specific conditions.

In most cases, developers apply a single condition using filter(). However, real-world applications often require filtering data using multiple conditions.

There are multiple ways to achieve this:

  • Calling filter() multiple times
  • Combining predicates using Predicate.and()
  • Combining predicates using Predicate.or()
  • Using Predicate.negate()
  • Using Predicate.isEqual()

This guide demonstrates all these approaches with examples.


2. Stream.filter() with a Single Condition

The filter() method accepts a Predicate<T>.

A predicate is a functional interface that evaluates a condition and returns:

  • true → element is retained
  • false → element is removed

Example

package com.javaprogramto.java8.streams.filter;

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FilterSingleCondition {

    public static void main(String[] args) {

        System.out.println("Fruits stream : "
                + getStream().collect(Collectors.toList()));

        // Filter 1
        Predicate<String> noFruitWordFilter =
                name -> !name.contains("fruit");

        List<String> filteredList =
                getStream()
                        .filter(noFruitWordFilter)
                        .collect(Collectors.toList());

        System.out.println("filteredList : " + filteredList);

        // Filter 2
        Predicate<String> noLetterOFilter =
                name -> !name.contains("o");

        List<String> noLetterOFilterList =
                getStream()
                        .filter(noLetterOFilter)
                        .collect(Collectors.toList());

        System.out.println("noLetterOFilterList : "
                + noLetterOFilterList);
    }

    private static Stream<String> getStream() {
        return Stream.of(
                "mango",
                "grapes",
                "apple",
                "papaya",
                "jack fruit",
                "dragon fruit"
        );
    }
}

Output

Fruits stream : [mango, grapes, apple, papaya, jack fruit, dragon fruit]

filteredList : [mango, grapes, apple, papaya]

noLetterOFilterList : [grapes, apple, papaya, jack fruit]

Explanation

Filter 1

name -> !name.contains("fruit")

Removes:

  • jack fruit
  • dragon fruit

Result:

[mango, grapes, apple, papaya]

Filter 2

name -> !name.contains("o")

Removes:

  • mango
  • dragon fruit

Result:

[grapes, apple, papaya, jack fruit]

3. Stream.filter() with Multiple Conditions

There are two common approaches:

  1. Apply multiple filter() calls
  2. Combine predicates using Predicate.and()

3.1 Calling filter() Multiple Times

Each filter() acts as an additional condition.

Example

package com.javaprogramto.java8.streams.filter;

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FilterMultipleCondition {

    public static void main(String[] args) {

        Predicate<String> noFruitWordFilter =
                name -> !name.contains("fruit");

        Predicate<String> noLetterOFilter =
                name -> !name.contains("o");

        List<String> result =
                getStream()
                        .filter(noFruitWordFilter)
                        .filter(noLetterOFilter)
                        .collect(Collectors.toList());

        System.out.println("Final result : " + result);
    }

    private static Stream<String> getStream() {
        return Stream.of(
                "mango",
                "grapes",
                "apple",
                "papaya",
                "jack fruit",
                "dragon fruit"
        );
    }
}

Output

Final result : [grapes, apple, papaya]

Execution Flow

Original Data

[mango, grapes, apple, papaya, jack fruit, dragon fruit]

After First Filter

.filter(noFruitWordFilter)

Removes:

jack fruit
dragon fruit

Result:

[mango, grapes, apple, papaya]

After Second Filter

.filter(noLetterOFilter)

Removes:

mango

Result:

[grapes, apple, papaya]

3.2 Using Predicate.and()

Instead of multiple filter() calls, predicates can be combined into a single predicate.

Example

Predicate<String> noFruitWordFilter =
        name -> !name.contains("fruit");

Predicate<String> noLetterOFilter =
        name -> !name.contains("o");

List<String> andPredicateResult =
        getStream()
                .filter(
                        noFruitWordFilter.and(noLetterOFilter)
                )
                .collect(Collectors.toList());

System.out.println(andPredicateResult);

Output

[grapes, apple, papaya]

Explanation

p1.and(p2)

is equivalent to:

p1.test(value) && p2.test(value)

Both predicates must return true.

Internally:

name -> !name.contains("fruit")
        && !name.contains("o")

4. Using Multiple Predicates with and()

You can chain as many predicates as required.

Example

Predicate<String> p1 = name -> !name.contains("fruit");
Predicate<String> p2 = name -> !name.contains("o");
Predicate<String> p3 = name -> name.length() > 5;

List<String> result =
        getStream()
                .filter(
                        p1.and(p2).and(p3)
                )
                .collect(Collectors.toList());

Equivalent to:

name -> !name.contains("fruit")
        && !name.contains("o")
        && name.length() > 5

5. Using Predicate.or()

Use or() when at least one condition should be true.

Example

Predicate<String> containsFruit =
        name -> name.contains("fruit");

Predicate<String> containsO =
        name -> name.contains("o");

List<String> result =
        getStream()
                .filter(
                        containsFruit.or(containsO)
                )
                .collect(Collectors.toList());

Output

[mango, jack fruit, dragon fruit]

Equivalent Logic

name.contains("fruit")
|| name.contains("o")

6. Using Predicate.negate()

negate() reverses a predicate.

Example

Predicate<String> containsFruit =
        name -> name.contains("fruit");

List<String> result =
        getStream()
                .filter(containsFruit.negate())
                .collect(Collectors.toList());

Equivalent

name -> !name.contains("fruit")

Output

[mango, grapes, apple, papaya]

7. Real-World Example with Employee Objects

Employee Class

class Employee {

    private String name;
    private int age;
    private double salary;

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public double getSalary() {
        return salary;
    }

    @Override
    public String toString() {
        return name;
    }
}

Filtering Employees

List<Employee> employees = List.of(
        new Employee("John", 25, 50000),
        new Employee("David", 35, 90000),
        new Employee("Mike", 40, 120000),
        new Employee("Sam", 28, 60000)
);

List<Employee> result =
        employees.stream()
                .filter(emp -> emp.getAge() > 30)
                .filter(emp -> emp.getSalary() > 80000)
                .collect(Collectors.toList());

System.out.println(result);

Output

[David, Mike]

Combined Predicate Version

Predicate<Employee> ageFilter =
        emp -> emp.getAge() > 30;

Predicate<Employee> salaryFilter =
        emp -> emp.getSalary() > 80000;

List<Employee> result =
        employees.stream()
                .filter(
                        ageFilter.and(salaryFilter)
                )
                .collect(Collectors.toList());

8. Performance Considerations

Multiple filter() Calls

stream
    .filter(p1)
    .filter(p2)
    .filter(p3)

Combined Predicate

stream
    .filter(
        p1.and(p2).and(p3)
    )

Both are lazy operations and generally provide similar performance.

Choose based on readability:

  • Use multiple filter() calls for readability.
  • Use Predicate.and() when predicates are reusable.

Example:

stream
    .filter(activeUserFilter)
    .filter(premiumUserFilter)
    .filter(emailVerifiedFilter)

This is often easier to read than:

stream.filter(
    activeUserFilter
        .and(premiumUserFilter)
        .and(emailVerifiedFilter)
);

9. Summary

ApproachDescription
filter(predicate)Single condition filtering
filter().filter()Multiple conditions using separate filters
Predicate.and()All conditions must be true
Predicate.or()At least one condition must be true
Predicate.negate()Reverse a condition
Predicate.isEqual()Equality comparison

Key Takeaways

  • filter() accepts a Predicate.
  • Multiple conditions can be applied by chaining filter() methods.
  • Predicate.and() combines conditions using logical AND (&&).
  • Predicate.or() combines conditions using logical OR (||).
  • Predicate.negate() reverses the result of a predicate.
  • Both chained filters and combined predicates produce the same logical result.
  • Choose the approach that improves readability and maintainability.

Post a Comment

Previous Post Next Post