filter()
Keep only the elements that match a predicate, without touching the source collection, and know when filtering belongs in the database instead.
Stream<T> filter(Predicate<? super T> predicate)- Added in Java Java 8
Parameters
| Parameter | Type | Description |
|---|---|---|
$predicate | Predicate<? super T> | A non-interfering, stateless test applied to each element. Elements for which it returns true are kept. |
Return value
Stream<T>A new stream containing only the elements for which the predicate returns true, in the same encounter order.
Errors
Nothing directly. Exceptions thrown by the predicate surface when the terminal operation runs. Passing a null predicate throws NullPointerException.
filter is an intermediate operation: it returns a new stream that lets through only the elements whose predicate returns true. Nothing happens until a terminal operation such as toList() or count() runs, and the source list is never changed.
Writing the predicate
The argument is a Predicate<? super T>, usually a lambda (o -> o.status() == Status.PAID) or a method reference (User::isActive). Two styles for several conditions both work:
orders.stream().filter(o -> o.paid()).filter(o -> o.total() > 100)
orders.stream().filter(o -> o.paid() && o.total() > 100)Chained filters read well when each condition has a name. For reusable rules, build Predicate objects and combine them with and, or and negate. Predicate.not(String::isBlank) (Java 11) negates a method reference, which you cannot do with !.
Keep the predicate pure
The predicate should look at the element and return an answer. Adding to a list, incrementing a counter or changing the element inside filter makes the code depend on how many times and in what order the predicate runs. Laziness means it may run for fewer elements than you expect: with findFirst(), the stream stops at the first match.
filter versus removeIf
filter produces a new result and leaves the source alone. Collection.removeIf mutates the collection in place and returns whether anything was removed. Pick removeIf when you own a mutable list and want it trimmed; pick filter when the source is shared, immutable, or a query result you should not change.
Filter in the database, not in memory
The most expensive mistake with filter in back-end code is loading every row and filtering in Java: repository.findAll().stream().filter(u -> u.tenantId() == id). That pulls the whole table over the network and into the heap. Put the condition in the WHERE clause and use stream filtering for rules that cannot be expressed in SQL, or for small in-memory lists such as configuration and already-paged results.
Nulls
A predicate that calls a method on a null element throws NullPointerException. Put .filter(Objects::nonNull) first when the source can hold nulls, for example after mapping through a lookup table.
Example
When to use it
- Return only the orders a customer is allowed to see after the database has already scoped them to the tenant.
- Drop blank or malformed lines from an uploaded CSV before mapping them to import commands.
- Pick out the feature flags that are enabled for the current environment from a small configuration list.
Frequently asked questions
How do I filter a stream by multiple conditions in Java?
Either combine them in one lambda with && and ||, chain several filter calls (each must pass), or build Predicate objects and join them with and(), or() and negate(). They produce the same result; chained or named predicates are easier to read and reuse.
Does Stream.filter modify the original list?
No. filter returns a new stream and the terminal operation builds a new result; the source collection is unchanged. If you want to remove elements from a mutable list in place, use Collection.removeIf.
How do I remove nulls from a stream?
Add .filter(Objects::nonNull) before any step that calls methods on the elements. For a single value that might be null, Stream.ofNullable(value) gives an empty stream instead of a stream containing null.
More examples
Combining named predicates
Named predicates turn validation rules into values you can combine and reuse across endpoints. Predicate.not wraps a method reference, which a leading ! cannot do.
void main() {
List<String> input = List.of("[email protected]", " ", "[email protected]", "", "[email protected]");
Predicate<String> notBlank = Predicate.not(String::isBlank);
Predicate<String> exampleDomain = s -> s.endsWith("@example.com");
IO.println(input.stream().filter(notBlank).toList());
IO.println(input.stream().filter(notBlank.and(exampleDomain)).toList());
IO.println(input.stream().filter(notBlank.and(exampleDomain.negate())).toList());
}
// Output:
// [[email protected], [email protected], [email protected]]
// [[email protected], [email protected]]
// [[email protected]]filter is lazy and stops early with findFirst
The predicate never sees 5 or 20, because findFirst has its answer at 12. Any side effect placed in a predicate runs only for the elements that were actually pulled.
void main() {
Optional<Integer> first = Stream.of(3, 8, 12, 5, 20)
.filter(n -> {
IO.println("checking " + n);
return n > 10;
})
.findFirst();
IO.println(first.orElse(-1));
}
// Output:
// checking 3
// checking 8
// checking 12
// 12filter leaves the source alone, removeIf does not
Use filter when you want a new result and removeIf when you deliberately want to trim a mutable collection you own, such as an in-memory session store.
void main() {
List<String> sessions = new ArrayList<>(List.of("active:1", "expired:2", "active:3"));
List<String> active = sessions.stream()
.filter(s -> s.startsWith("active"))
.toList();
IO.println(active);
IO.println(sessions);
boolean removed = sessions.removeIf(s -> s.startsWith("expired"));
IO.println(removed + " " + sessions);
}
// Output:
// [active:1, active:3]
// [active:1, expired:2, active:3]
// true [active:1, active:3]
Discussion