Back to Stream: filter
Q11Question 11 of 19

Filter all employees whose salary is greater than 50,000

Key Concepts
  • filter(Predicate) is a lazy intermediate operation that keeps elements where the predicate returns true and discards the rest.
  • For money comparisons, always use BigDecimal.compareTo, never double or float, because floating-point loses precision.
  • filter does not mutate the source collection; it produces a new stream that you materialise with collect or toList.
  • Multiple filters can be chained and the stream engine fuses them so each element is tested once as it flows through.
  • Predicate combinators and(), or(), negate() let you compose reusable named predicates for complex business rules.
  • Short-circuiting means Java stops evaluating the AND chain at the first false, avoiding unnecessary work.
  • In parallel streams filter is thread-safe because it reads each element independently with no shared state.

Definition

filter(Predicate) keeps elements that pass a boolean test and discards the rest, and for salary comparisons you must use BigDecimal.compareTo to avoid floating-point errors.
  • filter(Predicate) – intermediate, lazy operation that retains elements for which the predicate returns true.
  • BigDecimal for money – salary.compareTo(new BigDecimal("50000")) > 0 compares precisely; never use double > 50000.
  • Non-destructive – the source List is untouched; filter returns a new stream.
  • Fusible – chained filters are merged internally so each element passes through all tests in a single traversal.
  • Composable – named Predicate instances combine with and(), or(), and negate().

The HR manager at National Bank of India stands at the door with one rule: 'Only let through employees who earn more than Rs.50,000.' Rajesh earns Rs.85,000 and walks through. A junior clerk earning Rs.30,000 is stopped. filter is exactly that door with a yes-or-no rule.

Understanding the Concepts

What does filter do?

Stream.filter(Predicate) is an intermediate operation. It tests every element against a boolean condition and keeps only those where the condition is true. It is lazy: nothing happens until a terminal operation like collect or toList triggers the pipeline.

How do you filter employees by salary?

JAVA
package com.bank.service;

import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;

public class SalaryFilterDemo {
    record Employee(String name, String department, BigDecimal salary) {}

    public static void main(String[] args) {
        List<Employee> staff = List.of(
            new Employee("Rajesh", "IT", new BigDecimal("85000")),
            new Employee("Priya", "Finance", new BigDecimal("72000")),
            new Employee("Amit", "IT", new BigDecimal("30000")),
            new Employee("Deepa", "HR", new BigDecimal("50000")),
            new Employee("Vikram", "Finance", new BigDecimal("110000"))
        );

        BigDecimal threshold = new BigDecimal("50000");

        List<Employee> highEarners = staff.stream()
            .filter(e -> e.salary().compareTo(threshold) > 0)
            .toList();

        highEarners.forEach(e ->
            System.out.println(e.name() + ": Rs." + e.salary()));
    }
}
TEXT
Rajesh: Rs.85000
Priya: Rs.72000
Vikram: Rs.110000

Deep earns exactly Rs.50,000 but the condition is strictly greater than, so she does not qualify. This boundary detail matters in interviews.

Why must you use BigDecimal for money?

JAVA
// WRONG: double loses precision
double salary = 50000.01;
double threshold = 50000.00;
System.out.println(salary > threshold); // may give wrong answer for edge values

// CORRECT: BigDecimal compares exactly
BigDecimal salary = new BigDecimal("50000.01");
BigDecimal threshold = new BigDecimal("50000.00");
System.out.println(salary.compareTo(threshold) > 0); // true, always correct

BigDecimal stores the exact decimal value. A bank can never afford a rounding error on salary or balance.

How does filter differ from a for-loop?

| Aspect | filter | for-loop |
|--------|--------|----------|
| Style | Declarative | Imperative |
| Mutability | Source untouched | Often mutates a result list |
| Laziness | Lazy (deferred) | Eager (runs immediately) |
| Parallelism | Built-in via parallelStream | Manual threading |
| Readability | Pipeline reads left to right | Loop body can be complex |

JAVA
// Imperative for-loop equivalent
List<Employee> result = new ArrayList<>();
for (Employee e : staff) {
    if (e.salary().compareTo(threshold) > 0) {
        result.add(e);
    }
}

How do you compose predicates for reusable rules?

JAVA
import java.util.function.Predicate;

Predicate<Employee> highSalary = e -> e.salary().compareTo(new BigDecimal("50000")) > 0;
Predicate<Employee> itDept = e -> "IT".equals(e.department());

// Combine with and()
List<Employee> highEarningIT = staff.stream()
    .filter(highSalary.and(itDept))
    .toList();
// [Rajesh: Rs.85000]

// Negate: employees NOT earning high salary
List<Employee> lowEarners = staff.stream()
    .filter(highSalary.negate())
    .toList();

What are common mistakes?

JAVA
// WRONG: using == for String comparison
staff.stream().filter(e -> e.department() == "IT"); // reference comparison, may fail

// CORRECT: using equals
staff.stream().filter(e -> "IT".equals(e.department())); // null-safe, value comparison

// WRONG: using double for money
staff.stream().filter(e -> e.getSalaryDouble() > 50000.0); // floating-point imprecision

// CORRECT: using BigDecimal
staff.stream().filter(e -> e.salary().compareTo(threshold) > 0);

What are edge cases?

  • Exactly on the boundary: Rs.50,000 does not pass > 0 comparison. Use >= 0 for inclusive.
  • Empty stream: filter on an empty list produces an empty result, no exception.
  • Null fields: if salary can be null, guard with e.salary() != null && before compareTo.
  • All match: if every employee earns above 50,000, the result equals the original list (but as a new collection).
  • None match: if no employee earns above 50,000, the result is an empty list.

What about parallel streams?

filter is stateless and thread-safe. Each element is tested independently with no shared mutable state.

JAVA
List<Employee> highEarners = staff.parallelStream()
    .filter(e -> e.salary().compareTo(threshold) > 0)
    .toList(); // thread-safe, order preserved by toList

What should a senior engineer know?

  • filter does not modify the original list. The source List stays authoritative.
  • Chained filters are fused internally: stream.filter(A).filter(B) tests each element against A and B in one pass, not two separate passes.
  • For large datasets, put the most selective predicate first so short-circuiting skips more elements.
  • In Spring Data, prefer database-level WHERE clauses over in-memory filter for large result sets.

Visual Overview

DIAGRAM
  filter pipeline:
  ────────────────
  Source: [Rajesh:85K, Priya:72K, Amit:30K, Deepa:50K, Vikram:110K]
       │
  filter(salary > 50000)
       │
  Rajesh:85K  ✓ keep
  Priya:72K   ✓ keep
  Amit:30K    ✗ discard
  Deepa:50K   ✗ discard (NOT strictly greater)
  Vikram:110K ✓ keep
       │
  Result: [Rajesh:85K, Priya:72K, Vikram:110K]

Key Takeaway

Filtering a stream means keeping only the elements that satisfy a condition and discarding the rest. filter takes a Predicate and tests every element. Only those returning true survive into the result, which you materialise with collect or toList.

For salary comparisons in banking, always use BigDecimal and its compareTo method. Double and float lose precision, and even a tiny rounding error is unacceptable for money. The boundary detail matters: strictly greater than 50,000 excludes exactly 50,000.

filter is lazy, non-destructive, and fusible. It builds a pipeline that does nothing until a terminal operation runs, never modifies the original list, and fuses chained filters so each element is examined exactly once. Named predicates with and(), or(), and negate() make complex business rules readable and testable.

Remember one sentence: filter keeps elements where the predicate is true, and for money always use BigDecimal.compareTo, never double.

---

Share
Use ← → arrow keys to navigate