A Predicate in Java is a functional interface from the java.util.function package whose single method, test(T t), takes one argument and returns true or false. Because it is a functional interface, we create a Predicate with a lambda expression or a method reference and pass the condition around like any other value.
We use predicates wherever code needs a yes-or-no rule, such as the condition in Stream.filter(), the rule in List.removeIf(), a validation check or the filters on a search page. Named predicates can be combined, negated and unit-tested on their own.
The following example creates two predicates and uses every method of the interface.
Predicate<String> isLong = s -> s.length() > 5;
Predicate<String> startsWithB = s -> s.startsWith("b");
boolean longWord = isLong.test("banana"); // true
boolean both = isLong.and(startsWithB).test("banana"); // true
boolean either = isLong.or(startsWithB).test("kiwi"); // false
boolean notLong = isLong.negate().test("kiwi"); // true
List<String> shortWords = Stream.of("apple", "banana", "kiwi").filter(Predicate.not(isLong)).toList(); // [apple, kiwi]
boolean same = Predicate.isEqual("kiwi").test("kiwi"); // true
Notice that and(), or() and negate() return a new Predicate and leave the original unchanged. We go through creating predicates, chaining them with and() and or(), negating them with Predicate.not(), the primitive versions such as IntPredicate, and turning a regular expression into a predicate, with a search filter example at the end.
1. What Is a Predicate in Java?
Mathematics calls a function that returns true or false for each input a predicate, and Java uses the name for the same idea. The interface has one abstract method, test(), plus default and static methods that build new predicates from existing ones. Java 8 added Predicate, and Java 11 added the static not() method.

Each method of the interface either tests a value or returns a new predicate. Only test() runs a condition. The other methods build new predicates.
| Method | Kind | Returns | What it does |
|---|---|---|---|
| test(T t) | abstract | boolean | Evaluates the condition for one value |
| and(other) | default | Predicate | True when both are true; skips other when the first is false |
| or(other) | default | Predicate | True when either is true; skips other when the first is true |
| negate() | default | Predicate | Reverses the result |
| Predicate.not(p) | static, Java 11 | Predicate | Same as p.negate(), written as a method call |
| Predicate.isEqual(o) | static | Predicate | True when the input equals o according to Objects.equals() |
A Predicate<T> looks like a Function<T, Boolean>, but it returns a primitive boolean, so no Boolean object is created, and it comes with the composition methods above. Methods such as filter(), removeIf() and anyMatch() accept a Predicate and nothing else.
2. Creating a Predicate
Any lambda or method reference that takes one argument and returns a boolean can be assigned to a Predicate. The compiler checks the parameter type from the generic type on the left side.
2.1. Simple Predicate With a Lambda
A lambda is the usual way to write a one-off condition. We give the predicate a name that reads like a question, so the code that uses it reads like a sentence.
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<String> hasDigit = s -> s.chars().anyMatch(Character::isDigit);
boolean even = isEven.test(10); // true
boolean digit = hasDigit.test("room42"); // true
2.2. Predicate From a Method Reference
When a method already answers the question, a method reference is shorter than a lambda. The method must take the tested value as its argument or be called on it.
Predicate<String> isBlank = String::isBlank;
Predicate<Object> isNull = Objects::isNull;
Predicate<String> inStock = Set.of("apple", "kiwi")::contains;
boolean blank = isBlank.test(" "); // true
boolean missing = isNull.test(null); // true
boolean available = inStock.test("kiwi"); // true
2.3. Predicate as a Method Parameter
A method that accepts a Predicate lets the caller decide the condition. The wildcard ? super T allows a broader predicate, such as a Predicate<Object>, for a list of any type.
static <T> List<T> select(List<T> items, Predicate<? super T> rule) {
return items.stream().filter(rule).toList();
}
List<Integer> numbers = List.of(3, 8, 12, 5);
List<Integer> big = select(numbers, n -> n > 6); // [8, 12]
List<Integer> notNull = select(numbers, Objects::nonNull); // [3, 8, 12, 5]
3. Chaining Predicates With and() and or()
Real conditions combine several checks, and chaining keeps each check small and named. The method and() returns a predicate that is true only when both predicates are true, and or() returns one that is true when at least one is true.
Both methods short-circuit like && and ||. If the first predicate decides the result, the second one never runs. If the first predicate throws an exception, the exception reaches the caller and the second predicate is not evaluated.
Predicate<String> notNull = Objects::nonNull;
Predicate<String> notEmpty = s -> !s.isEmpty();
boolean nullOk = notNull.and(notEmpty).test(null); // false, isEmpty() never runs
boolean textOk = notNull.and(notEmpty).test("hi"); // true
The order matters for safety. Swapping the two predicates in the code above calls isEmpty() on null first.
Predicate<String> notEmpty = s -> !s.isEmpty();
Predicate<String> notNull = Objects::nonNull;
boolean crash = notEmpty.and(notNull).test(null); // NullPointerException
3.1. Evaluation Order of a Predicate Chain
A chain such as a.or(b).and(c) is evaluated from left to right, so it means (a || b) && c, not a || (b && c). The usual precedence of && over || does not apply to method calls. When we want the other grouping, we nest the call.
Predicate<Integer> small = n -> n < 10;
Predicate<Integer> even = n -> n % 2 == 0;
Predicate<Integer> positive = n -> n > 0;
boolean chained = small.or(even).and(positive).test(-4); // false, (small || even) && positive
boolean nested = small.or(even.and(positive)).test(-4); // true, small || (even && positive)
3.2. Combining a List of Predicates
Search screens and rule engines often build a list of conditions at runtime. We reduce the list to one predicate with Predicate::and, starting from a predicate that is always true, or with Predicate::or, starting from one that is always false.
List<Predicate<String>> rules = List.of(s -> s.length() >= 4, s -> s.startsWith("p"), s -> !s.contains(" "));
Predicate<String> allRules = rules.stream().reduce(s -> true, Predicate::and);
Predicate<String> anyRule = rules.stream().reduce(s -> false, Predicate::or);
boolean pear = allRules.test("pear"); // true
boolean fig = allRules.test("fig"); // false
boolean figAny = anyRule.test("fig"); // true
4. Negating a Predicate With negate() and Predicate.not()
A negated predicate matches every element that the original predicate rejects. The default method negate() works on a predicate variable, and the static Predicate.not(), added in Java 11, wraps any predicate, including a method reference.
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isOdd = isEven.negate();
Predicate<Integer> alsoOdd = Predicate.not(isEven);
boolean odd = isOdd.test(7); // true
boolean odd2 = alsoOdd.test(7); // true
The real benefit of Predicate.not() shows up with method references. A method reference cannot call negate() on itself without a cast, so before Java 11 we had to fall back to a lambda.
List<String> lines = List.of("milk", " ", "", "eggs");
List<String> filled = lines.stream().filter(Predicate.not(String::isBlank)).toList(); // [milk, eggs]
List<String> filled2 = lines.stream().filter(s -> !s.isBlank()).toList(); // [milk, eggs]
// does not compile: a method reference has no type until it is assigned
List<String> filled = lines.stream().filter(String::isBlank.negate()).toList();
5. Using a Predicate With Streams and Collections
The JDK accepts a Predicate in many places beyond Stream.filter(). The same named predicate works in all of them, so one rule such as isExpensive serves a stream, a list cleanup and an Optional check.
| API | What the predicate decides | Example result |
|---|---|---|
| Stream.filter(p) | Which elements continue | A stream of the matching elements |
| Collection.removeIf(p) | Which elements are removed from a mutable collection | true if anything was removed |
| Stream.anyMatch/allMatch/noneMatch(p) | Whether some, all or no elements match | A boolean |
| Collectors.partitioningBy(p) | Which of the two groups an element goes to | Map<Boolean, List<T>> |
| Stream.takeWhile(p) / dropWhile(p) | Where an ordered stream is cut | The leading elements, or the rest |
| Optional.filter(p) | Whether the value is kept | The same Optional or an empty one |
The prices in the following example are cents, and one predicate drives every call.
Predicate<Integer> isExpensive = cents -> cents > 1000;
List<Integer> prices = List.of(450, 1200, 999, 2500);
List<Integer> expensive = prices.stream().filter(isExpensive).toList(); // [1200, 2500]
boolean anyExpensive = prices.stream().anyMatch(isExpensive); // true
Map<Boolean, List<Integer>> split = prices.stream().collect(Collectors.partitioningBy(isExpensive)); // {false=[450, 999], true=[1200, 2500]}
List<Integer> cheapStart = prices.stream().takeWhile(isExpensive.negate()).toList(); // [450]
Optional<Integer> checked = Optional.of(999).filter(isExpensive); // Optional.empty
List<Integer> cart = new ArrayList<>(prices);
boolean removed = cart.removeIf(isExpensive); // true
List<Integer> left = cart; // [450, 999]
Notice that removeIf() changes the list in place, so we call it on a mutable copy. Calling it on the unmodifiable List.of() list throws UnsupportedOperationException.
6. BiPredicate and Primitive Predicates
The java.util.function package has a few relatives of Predicate for other argument shapes. A BiPredicate tests two arguments, and the primitive versions test an int, long or double without boxing it into a wrapper object.
| Interface | Method | Typical source |
|---|---|---|
| BiPredicate<T, U> | boolean test(T t, U u) | Two values, such as a key and a value of a map |
| IntPredicate | boolean test(int value) | IntStream.filter() |
| LongPredicate | boolean test(long value) | LongStream.filter() |
| DoublePredicate | boolean test(double value) | DoubleStream.filter() |
6.1. BiPredicate for Two Arguments
A BiPredicate fits checks that need two inputs, for example a word and the maximum length allowed for it. It has and(), or() and negate(), but no not() or isEqual().
BiPredicate<String, Integer> fitsIn = (word, max) -> word.length() <= max;
boolean fits = fitsIn.test("kiwi", 5); // true
boolean tooLong = fitsIn.test("banana", 5); // false
6.2. IntPredicate and Other Primitive Predicates
An IntPredicate works with IntStream and avoids creating an Integer for every number. It offers and(), or() and negate() but no static not(), so we call negate() when we need the opposite condition. We cover the primitive streams themselves in primitive type streams.
static boolean isPrime(int n) {
return n > 1 && IntStream.rangeClosed(2, (int) Math.sqrt(n)).noneMatch(d -> n % d == 0);
}
IntPredicate isOdd = n -> n % 2 != 0;
IntPredicate isOddPrime = isOdd.and(n -> isPrime(n));
int[] oddPrimes = IntStream.range(1, 20).filter(isOddPrime).toArray(); // [3, 5, 7, 11, 13, 17, 19]
boolean negativeOdd = isOdd.test(-3); // true
IntPredicate wrongOdd = n -> n % 2 == 1;
boolean missed = wrongOdd.test(-3); // false, -3 % 2 is -1
The last two lines show a common bug. In Java, the remainder of a negative number is negative, so n % 2 == 1 misses negative odd numbers, and n % 2 != 0 handles both signs.
7. Regular Expressions as Predicates
A compiled regular expression can serve as a predicate without a lambda. The class Pattern has two methods for that, and they differ in how much of the string must match.
- The method asPredicate() returns a predicate that calls find(), so it is true when the pattern occurs anywhere in the string.
- The method asMatchPredicate(), added in Java 11, returns a predicate that calls matches(), so the whole string must match the pattern.
Pattern digits = Pattern.compile("\\d+");
boolean found = digits.asPredicate().test("room 42"); // true
boolean whole = digits.asMatchPredicate().test("room 42"); // false
boolean onlyDigits = digits.asMatchPredicate().test("42"); // true
The following example keeps the e-mail addresses of one domain. The dot in example.com is escaped, because an unescaped dot matches any character and would accept alex@exampleXcom.
Predicate<String> ourDomain = Pattern.compile("[\\w.+-]+@example\\.com").asMatchPredicate();
List<String> emails = List.of("alex@example.com", "bob@mail.com", "eve@example.com.evil.io", "dana@example.com");
List<String> ours = emails.stream().filter(ourDomain).toList(); // [alex@example.com, dana@example.com]
boolean looseDot = Pattern.compile("^(.+)@example.com$").asPredicate().test("alex@exampleXcom"); // true, the dot matches X
We compile the Pattern once and reuse the predicate. Calling String.matches() inside a lambda compiles the regex again for every element.
8. Building Search Filters for an Apartment Listing
A rental site lets visitors filter apartments by city, maximum rent and whether pets are allowed, and every filter is optional. Writing an if block for each combination of filters grows fast, whereas predicates let us add one condition per filter that the visitor filled in.
record Apartment(String city, int rent, boolean petsAllowed) {}
static Predicate<Apartment> searchFilter(Optional<String> city, OptionalInt maxRent, boolean petsRequired) {
Predicate<Apartment> filter = apartment -> true;
if (city.isPresent()) {
filter = filter.and(apartment -> apartment.city().equalsIgnoreCase(city.get()));
}
if (maxRent.isPresent()) {
filter = filter.and(apartment -> apartment.rent() <= maxRent.getAsInt());
}
if (petsRequired) {
filter = filter.and(Apartment::petsAllowed);
}
return filter;
}
The method starts with a predicate that accepts everything and adds a condition only for the filters that are set. The call site applies the result with one filter(), and the same predicate can also check a single listing before it is saved.
List<Apartment> listings = List.of(new Apartment("Berlin", 1200, true), new Apartment("Berlin", 1800, false), new Apartment("Munich", 1500, true));
Predicate<Apartment> visitorFilter = searchFilter(Optional.of("berlin"), OptionalInt.of(1500), false);
List<Apartment> hits = listings.stream().filter(visitorFilter).toList(); // [Apartment[city=Berlin, rent=1200, petsAllowed=true]]
long petFriendly = listings.stream().filter(searchFilter(Optional.empty(), OptionalInt.empty(), true)).count(); // 2
Each condition is small enough to test alone, and a new filter, such as a minimum number of rooms, is one more if with one more and(). The visitor’s input never reaches a long if-else chain.
9. Java Predicate FAQs
Predicate questions mostly compare it with Function or ask about isEqual(), checked exceptions and the choice between not() and negate().
9.1. What Is the Difference Between Predicate and Function in Java?
A Predicate<T> always returns a primitive boolean from test(), whereas a Function<T, R> returns any type from apply(). A Function<T, Boolean> can express the same condition, but it boxes the result and has no and(), or() or negate(), and filter() does not accept it.
9.2. Is Predicate a Functional Interface?
Yes. Predicate is annotated with @FunctionalInterface and has only one abstract method, test(). Its other methods are default or static, so they do not count against the single-method rule.
9.3. What Does Predicate.isEqual() Do?
The method Predicate.isEqual() returns a predicate that compares its input with a fixed object using Objects.equals(). Because of that, it also works with null, where a lambda such as s -> target.equals(s) would throw NullPointerException if target were null.
Predicate<String> isNullValue = Predicate.isEqual(null);
boolean nullMatch = isNullValue.test(null); // true
boolean textMatch = isNullValue.test("kiwi"); // false
9.4. Can a Predicate Throw a Checked Exception?
No. The method test() declares no checked exceptions, so a lambda that calls, for example, Files.size() must catch the IOException inside the lambda or wrap it in an unchecked exception. We show the common patterns in handling exceptions in streams.
9.5. Should We Use Predicate.not() or negate()?
Both return the same result. We use Predicate.not() with method references, as in filter(Predicate.not(String::isBlank)), and negate() when we already have a predicate in a variable.
10. Conclusion
A Predicate holds a yes-or-no condition as a value. We create it from a lambda or a method reference, combine it with and() and or(), reverse it with negate() or Predicate.not(), and hand it to filter(), removeIf(), anyMatch() or partitioningBy().
Chains run from left to right and short-circuit, so we put cheap and null-safe checks first. For numbers in IntStream we use IntPredicate, and for text rules a compiled Pattern gives us asPredicate() for a partial match or asMatchPredicate() for a full match.
11. References
- Predicate Javadoc (Java 25)
- IntPredicate Javadoc
- BiPredicate Javadoc
- Pattern.asMatchPredicate() Javadoc
- java.util.function package summary
Happy Learning !!
is there any advantage to use predicate over legacy if-else ?
More readable code.
Using predicates will have performance impact on large collection of Objects right… ???
I have tried filtering out the even numbers from the a list containing 0-1500 using Guava Predicates and normal conventional way of for and if loop implementation .
I guess it will apply to the Java Se8 Predicate case as well right?
I also tried to find the difference in performance. You are right it’s much bigger than alternate ways of for loop iterations. Below is code I tried and executed it multiple times with almost same result.
public static void main(String[] args){ list = new ArrayList (); stream = list.stream(); List
for(int i = 1; i< 100000; i++){ list.add(i); } long startTime = System.currentTimeMillis(); Stream
Integer[] evenNumbersArr = stream.filter(i -> i%2 == 0).toArray(Integer[]::new);
System.out.println(evenNumbersArr);
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
startTime = System.currentTimeMillis();
Integer[] evenNumbersArr2 = new Integer[list.size()];
for(Integer i : list){
if(i%2 == 0) evenNumbersArr2[i] = i;
}
System.out.println(evenNumbersArr2);
endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
}
Output:
[Ljava.lang.Integer;@d46ca6
57
[Ljava.lang.Integer;@117d9a3
3
This means using streams, slows down the performance atleast in this case. I tried this too, and with traditional approach results are faster.
With a trivial example like this then yes the overhead of stream processing is probably not worth it. I think that one of the central tenets of streams is that you can chain operations together and they are ‘squashed’ and only evaluated once on the terminal operation, whereas pre-Java8 you would have to loop over the collection each time for each operation. The stream API also gives you the powerful option to parallelise the processing simply by calling .parallel() on an existing stream.
That’s true.
.parallel()really is very powerful in handling large datasets.Streams are useful when they are used parallel for large collections otherwise its an overhead to create streams.
I also had evaluated the performance for loops and parallel streams win in this case.
For small collections traditional or Java 5 way is useful.
Agree. There is a trade off between clean code and performance gain. For small collections , the performance gain is not that much but the code readability increase many fold. I will go for clean code in this case considering powerful hardware available these days.