Composing Predicates in Java 8
Fast recall
Quick revision
Core answer
Practical Java 8 Predicate composition — shows real usage beyond just definitions
Using && inside a lambda instead of and() method — works but misses the composition approach
Show how composed predicates make stream filters more readable and reusable
Interview-ready guide
Interview answer
You compose predicates using three methods: and, or, and negate.
Let's say you have two predicates. One checks if an order amount is over a thousand. Another checks if the status is pending.
If you want a predicate that's true only when both conditions are met, you use the and method. and(isPending) gives you a new predicate that returns true only when the order is high value AND pending. The or method works the same way but for either-or logic.
If you want to invert a predicate, use negate. negate() gives you a predicate for non-pending orders. The key thing is these methods return a NEW predicate.
They don't modify the original ones. Why is this better than just using double ampersand inside a lambda? Because you can define each condition once as a named predicate and reuse it across your codebase.
Instead of copying the same lambda logic everywhere, you compose predicates. It makes your stream filters way more readable. Instead of filter with a giant lambda full of boolean logic, you write filter with a composed predicate that has a meaningful name.
And you can test each predicate independently. Common mistake: freshers write the boolean logic inside lambdas instead of using composition. It works, but you miss the reusability.
Composing Predicates in Java 8
Direct Answer
Predicate has default methods and(), or(), and negate() for composition. p1.and(p2) creates a new Predicate that is true only when both are true. p1.or(p2) is true when either is true. p.negate() inverts the Predicate. These compose predicates without nesting lambdas.
Code Example
import java.util.List;
import java.util.function.Predicate;
record Order(long id, double amount, String status, String customerType) {}
public class OrderFilter {
private static final Predicate<Order> HIGH_VALUE =
order -> order.amount() > 1_000;
private static final Predicate<Order> PENDING =
order -> "PENDING".equals(order.status());
private static final Predicate<Order> PREMIUM =
order -> "PREMIUM".equals(order.customerType());
public List<Order> filterOrders(List<Order> orders) {
Predicate<Order> priority = HIGH_VALUE.and(PENDING).or(PREMIUM);
return orders.stream().filter(priority).toList();
}
public static void main(String[] args) {
var filter = new OrderFilter();
var orders = List.of(
new Order(1L, 1_500.0, "PENDING", "REGULAR"),
new Order(2L, 500.0, "COMPLETE", "PREMIUM")
);
System.out.println(filter.filterOrders(orders));
}
}
Common Mistakes & Pitfalls
- Using boolean operators instead of methods: Freshers write order -> isHighValue(order) && isPending(order) inside a lambda instead of using isHighValue.and(isPending). Both work, but the method-based approach allows reusing composed predicates.
- Not extracting reusable predicates: Freshers inline all logic in stream filters, repeating the same conditions. Extract conditions into named predicates so you can reuse and compose them.
- Confusing and() with logical AND: The and() method returns a NEW predicate — it doesn't modify the existing one. Freshers sometimes think isHighValue.and(isPending) changes isHighValue. It doesn't. You must use the returned composed predicate.
Check your understanding
Answer these without looking back at the article.
- What is the difference between Predicate.and() and writing && in a lambda?
- How does Function.andThen() work?
- What is Predicate.not() and when was it added?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.