Imagine searching an online store for products that are in stock and cost no more than 50 euros. Each product has to pass two checks:
product.stock > 0 && product.priceCents <= 5000
When I need that condition in several places, I want a name for the question it answers: “Does this product match my search?” Wrapping the check in a function gives me a predicate.
A predicate is a function that evaluates a condition and returns true or false. The examples here take one input and answer one question about it. Predicates can also take multiple inputs; the useful part is the boolean answer.
Java gives this idea a standard interface, Predicate<T>. In JavaScript, an ordinary function can do the same job.
Start with the question
Here is the JavaScript version:
const matchesSearch = product =>
product.stock > 0 && product.priceCents <= 5000;
matchesSearch({ stock: 5, priceCents: 2500 }); // true
matchesSearch({ stock: 0, priceCents: 2500 }); // false
matchesSearch({ stock: 5, priceCents: 8000 }); // false
Here, stock is the available quantity, and priceCents is a price in euro cents: 5000 means 50 euros. Assume both values are nonnegative integers. The name describes the decision, and calling the function gives its answer for a particular product.
That is different from a function that transforms a product into its name, or one that sends a notification. A predicate checks a condition. Keeping that purpose narrow makes it easier to reuse and reason about.
Java makes the contract explicit
In Java, Predicate<T> is a functional interface whose abstract method is boolean test(T value). A lambda can provide its implementation. Java Predicate API
This example uses Java 21:
import java.util.List;
import java.util.function.Predicate;
public class PredicateExample {
record Product(String name, int stock, int priceCents) {}
public static void main(String[] args) {
Predicate<Product> matchesSearch =
product -> product.stock() > 0 && product.priceCents() <= 5000;
var products = List.of(
new Product("Mouse", 5, 2500),
new Product("Keyboard", 3, 8000),
new Product("Headphones", 0, 4000)
);
System.out.println(matchesSearch.test(products.get(0))); // true
var matchingNames = products.stream()
.filter(matchesSearch)
.map(Product::name)
.toList();
System.out.println(matchingNames); // [Mouse]
}
}
Predicate<Product> says which input the check accepts. test evaluates it directly, while filter accepts the predicate and keeps matching elements. The following map performs a different job: it converts each retained product into a name. Java Stream API
The condition could also stay inline as .filter(product -> product.stock() > 0 && product.priceCents() <= 5000). I give it a name when the rule is reused or when the name helps explain the code.
The same rule in JavaScript
JavaScript does not require a Predicate interface. Pass a function to filter:
const products = [
{ name: "Mouse", stock: 5, priceCents: 2500 },
{ name: "Keyboard", stock: 3, priceCents: 8000 },
{ name: "Headphones", stock: 0, priceCents: 4000 }
];
const matchesSearch = product =>
product.stock > 0 && product.priceCents <= 5000;
const matchingNames = products
.filter(matchesSearch)
.map(product => product.name);
console.log(matchingNames); // ["Mouse"]
Array filter builds a new array containing the matching elements. Its callback receives the element, its index, and the array; our function only needs the element. ECMAScript Array filter
There is a useful difference between the languages here. Java requires test to return a boolean. JavaScript array methods interpret the callback's result as truthy or falsy. I prefer predicates that return actual booleans, so their contract stays clear.
For example, product => product.name tests truthiness: a name containing only spaces passes. If the question is whether the name contains text, I would write:
const hasName = product =>
typeof product.name === "string" && product.name.trim().length > 0;
Combine small rules
A named rule can be built from smaller predicates. Java supplies and, or, and negate:
Predicate<Product> isInStock = product -> product.stock() > 0;
Predicate<Product> isWithinBudget = product -> product.priceCents() <= 5000;
Predicate<Product> matchesSearch = isInStock.and(isWithinBudget);
Predicate<Product> isOverBudget = isWithinBudget.negate();
Predicate<Product> isExcluded = isInStock.negate().or(isOverBudget);
These declarations belong inside the Java example's main method, replacing its original matchesSearch declaration. matchesSearch requires both conditions. isOverBudget reverses the price check, while isExcluded matches a product that is out of stock or over budget.
Java's and stops when the first predicate returns false; or stops when it returns true. That short-circuit behavior matters when a later check depends on an earlier one. Predicate composition
In JavaScript, ordinary logical operators express the same rules:
const isInStock = product => product.stock > 0;
const isWithinBudget = product => product.priceCents <= 5000;
const matchesSearch = product => isInStock(product) && isWithinBudget(product);
const isOverBudget = product => !isWithinBudget(product);
const isExcluded = product => !isInStock(product) || isOverBudget(product);
Here, && and || also short-circuit. The comparisons return booleans, so these functions do too. I would keep the original two-condition check unless the smaller rules had a useful role of their own.
Filtering is only one use
Once the rule has a name, I can ask several questions with it:
| Question | Java stream | JavaScript array |
|---|---|---|
| Which products match? | filter(matchesSearch).toList() |
filter(matchesSearch) |
| Does any product match? | anyMatch(matchesSearch) |
some(matchesSearch) |
| Do all products match? | allMatch(matchesSearch) |
every(matchesSearch) |
The Java entries are suffixes of a fresh products.stream() call; the JavaScript entries follow products.. Java streams are consumed by terminal operations, so create a new stream for each question. Java Stream API
For an empty collection, anyMatch and some return false, while allMatch and every return true. There is no element that fails the “all” check. If a business rule also requires at least one product, check that separately. Java matching operations, ECMAScript every, ECMAScript some
Keep the check predictable
I try to keep predicates free of actions such as sending emails, modifying their input, or incrementing a counter. Matching operations may stop early, and Java stream evaluation is lazy. A predicate's purpose is easier to follow when it only answers its question.
There are two other boundaries worth making explicit:
- Input assumptions. These examples expect product objects with nonnegative integer quantities and prices in cents. If
nullis a valid input, decide what it means and handle it before accessing the fields. - Async checks in JavaScript. An
asyncfunction returns a Promise. Passing it to arrayfilterdoes not wait for the boolean inside; the Promise object is truthy. Resolve asynchronous checks first, then filter using their results. ECMAScript Array filter, ECMAScript async functions
A product matching these filters is a candidate to display. When the customer places an order, the backend still needs to check the current stock and price; the values used by a search page may have changed.
What I find useful about predicates is the name they give a decision. matchesSearch lets me read a filter in terms of the application's rules. Whether that rule lives in Java's Predicate<Product> or a JavaScript function, it remains a small question with an answer I can check directly.
