Homeβ€Ί Javaβ€Ί Java Stream filter(): Filter Collections with Lambdas

Java Stream filter(): Filter Collections with Lambdas

Where developers are forged. Β· Structured learning Β· Free forever.
πŸ“ Part of: Collections β†’ Topic 20 of 21
Master Java Stream.
πŸ§‘β€πŸ’» Beginner-friendly β€” no prior Java experience needed
In this tutorial, you'll learn:
  • filter() keeps only elements where the predicate returns true β€” it's lazy and doesn't execute until a terminal operation like collect() or count() is called.
  • Chain multiple filter() calls or combine conditions with && and || β€” multiple filter() calls are more readable for complex conditions.
  • filter(Objects::nonNull) is the standard idiom for removing nulls from a stream before further processing.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
⚑ Quick Answer
Stream.filter() is the Java streams version of 'keep only the elements that match this condition'. You pass a Predicate β€” a function that returns true or false for each element β€” and filter() keeps only the true ones. It's lazy: the filtering doesn't execute until a terminal operation (collect, count, findFirst) is called.

filter() is the building block of data processing in modern Java. The power comes not from filter() alone but from composing it with map(), flatMap(), sorted(), and collect() in a readable declarative pipeline that describes what you want, not how to iterate to get it.

filter() Basics, Multiple Conditions, and Chaining

StreamFilterExample.java Β· JAVA
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
package io.thecodeforge.collections;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class StreamFilterExample {

    enum PaymentStatus { PENDING, COMPLETED, FAILED, REFUNDED }
    record Payment(String id, String customerId, int amountPence, PaymentStatus status) {}

    public static void main(String[] args) {
        List<Payment> payments = List.of(
            new Payment("p1", "cust-1", 100_00, PaymentStatus.COMPLETED),
            new Payment("p2", "cust-2", 50_00,  PaymentStatus.FAILED),
            new Payment("p3", "cust-1", 250_00, PaymentStatus.COMPLETED),
            new Payment("p4", "cust-3", 75_00,  PaymentStatus.PENDING),
            new Payment("p5", "cust-1", 10_00,  PaymentStatus.REFUNDED)
        );

        // Basic filter β€” single condition
        List<Payment> completed = payments.stream()
            .filter(p -> p.status() == PaymentStatus.COMPLETED)
            .collect(Collectors.toList());
        System.out.println("Completed: " + completed.size()); // 2

        // Multiple conditions β€” AND with &&
        List<Payment> largCompletedForCust1 = payments.stream()
            .filter(p -> p.status() == PaymentStatus.COMPLETED)
            .filter(p -> p.customerId().equals("cust-1"))
            .filter(p -> p.amountPence() > 100_00)
            .collect(Collectors.toList());
        System.out.println("Large completed for cust-1: " + largCompletedForCust1.size()); // 1

        // Chain multiple filters as one predicate with &&
        List<Payment> same = payments.stream()
            .filter(p -> p.status() == PaymentStatus.COMPLETED
                      && p.customerId().equals("cust-1")
                      && p.amountPence() > 100_00)
            .collect(Collectors.toList());

        // filter + map + collect β€” the classic pipeline
        List<String> completedIds = payments.stream()
            .filter(p -> p.status() == PaymentStatus.COMPLETED)
            .map(Payment::id)
            .collect(Collectors.toList());
        System.out.println("Completed IDs: " + completedIds); // [p1, p3]

        // Null-safe filter
        List<String> ids = List.of("p1", null, "p3", null, "p5");
        List<String> nonNull = ids.stream()
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
        System.out.println("Non-null: " + nonNull); // [p1, p3, p5]

        // Count without collecting
        long failedCount = payments.stream()
            .filter(p -> p.status() == PaymentStatus.FAILED)
            .count();
        System.out.println("Failed count: " + failedCount); // 1
    }
}
β–Ά Output
Completed: 2
Large completed for cust-1: 1
Completed IDs: [p1, p3]
Non-null: [p1, p3, p5]
Failed count: 1
OperationMethodExample
Keep matching elementsfilter(predicate).filter(p -> p.amount() > 100)
Negate conditionfilter(predicate.negate()).filter(Predicate.not(String::isEmpty))
Null-safe filterfilter(Objects::nonNull).filter(Objects::nonNull)
Multiple conditions ANDfilter with &&.filter(p -> a && b)
Multiple conditions ORfilter with \|\|.filter(p -> a \|\| b)
Combine predicatesPredicate.and/or()p1.and(p2)

🎯 Key Takeaways

  • filter() keeps only elements where the predicate returns true β€” it's lazy and doesn't execute until a terminal operation like collect() or count() is called.
  • Chain multiple filter() calls or combine conditions with && and || β€” multiple filter() calls are more readable for complex conditions.
  • filter(Objects::nonNull) is the standard idiom for removing nulls from a stream before further processing.
  • For boolean checks (does any element match? do all elements match?), use anyMatch(), allMatch(), or noneMatch() instead of filter().count() > 0.

⚠ Common Mistakes to Avoid

  • βœ•Calling filter() on a stream with potential null elements without filtering nulls first β€” operations inside filter() lambdas will throw NPE if called on null elements.
  • βœ•Forgetting filter() is lazy β€” it doesn't execute until a terminal operation is called. Debugging filter() without a terminal op will show no filtering happening.
  • βœ•Mutating external state inside filter() β€” filter() is a pure function operation. Side effects inside lambdas are unpredictable in parallel streams.
  • βœ•Using filter().findFirst() when anyMatch() suffices β€” if you only need to know whether any element matches, anyMatch() is cleaner and short-circuits earlier.

Interview Questions on This Topic

  • QWhat is the difference between Stream.filter() and Stream.anyMatch()?
  • QHow would you filter a List<Payment> to only include completed payments over Β£100 using Java streams?
  • QIs Stream.filter() eager or lazy? What are the implications?

Frequently Asked Questions

How do I filter a list in Java 8+?

Use Stream.filter(): list.stream().filter(element -> condition).collect(Collectors.toList()). The filter() method takes a Predicate (a function returning boolean) and keeps only elements where the predicate returns true.

How do I filter null values from a Java stream?

Use filter(Objects::nonNull): stream.filter(Objects::nonNull).collect(Collectors.toList()). This removes all null elements before downstream operations, preventing NullPointerException in subsequent map() or other operations.

πŸ”₯
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousHashMap vs Hashtable in JavaNext β†’Java Map containsKey(): Check if a Key Exists
Forged with πŸ”₯ at TheCodeForge.io β€” Where Developers Are Forged