Functional Interface Predicate

This code looks right, compiles fine, and still prints true:
Predicate<String> notEmpty = String::isEmpty;
notEmpty.negate();
System.out.println(notEmpty.test("")); // true, although we expected false A predicate in Java is immutable: negate() changes nothing inside the object, it returns a new predicate. The line has to be written as Predicate<String> notEmpty = empty.negate();. Let's look at how Predicate works and what else it can do.
What is a Predicate in Java
Predicate is a built-in functional interface from the java.util.function package, added in Java SE 8. It takes one argument of type T and returns a boolean — the result of a condition check.
The term comes from logic: a predicate is a statement about an object that is either true or false. "The number is negative", "the string is empty", "the user is an adult" — each of these is a predicate. In Java such a statement is written as a lambda expression or a method reference.
Definition of the Predicate interface:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) { ... }
default Predicate<T> or(Predicate<? super T> other) { ... }
default Predicate<T> negate() { ... }
static <T> Predicate<T> isEqual(Object targetRef) { ... }
static <T> Predicate<T> not(Predicate<? super T> target) { ... } // Java 11
} There is exactly one abstract method here — test() — which is why the interface is annotated with @FunctionalInterface and can be implemented by a lambda. For the concept itself see the lesson on functional interfaces.
The function descriptor of the interface:
T -> boolean The test() method: first example
test() runs the check and returns its result. Here is a predicate that detects negative numbers:
import java.util.function.Predicate;
public class PredicateExample1 {
public static void main(String[] args) {
Predicate<Integer> negative = i -> i < 0;
System.out.println(negative.test(-6)); // true
System.out.println(negative.test(6)); // false
System.out.println(negative.test(0)); // false
}
} Note the last line: 0 is not a negative number, so the check returns false. A predicate always gives a strict yes or no — there is no third option.
Combining predicates: and(), or(), negate()
Besides test(), the interface provides three default methods that let you compose predicates instead of rewriting conditions by hand.
| Method | Signature | What it does | Code analogue |
|---|---|---|---|
| test | boolean test(T t) | The single abstract method: runs the check | the condition inside an if |
| and | default Predicate<T> and(Predicate<? super T> other) | Logical AND of two predicates, short-circuiting | a && b |
| or | default Predicate<T> or(Predicate<? super T> other) | Logical OR of two predicates, short-circuiting | a || b |
| negate | default Predicate<T> negate() | Negation of the current predicate | !a |
| isEqual | static <T> Predicate<T> isEqual(Object targetRef) | Predicate that compares via Objects.equals() | Objects.equals(x, target) |
| not | static <T> Predicate<T> not(Predicate<? super T> target) | Negation of the given predicate (Java 11) | !a |
All three of and(), or() and negate() return a new Predicate and never modify the original objects.
Example with the and() method:
import java.util.function.Predicate;
public class PredicateExample2 {
public static void main(String[] args) {
Predicate<String> containsA = t -> t.contains("A");
Predicate<String> containsB = t -> t.contains("B");
System.out.println(containsA.and(containsB).test("ABCD")); // true
System.out.println(containsA.and(containsB).test("ACD")); // false
}
} Example with or() and negate():
import java.util.function.Predicate;
public class PredicateExample3 {
public static void main(String[] args) {
Predicate<Integer> negative = i -> i < 0;
Predicate<Integer> even = i -> i % 2 == 0;
// negative OR even
System.out.println(negative.or(even).test(7)); // false
System.out.println(negative.or(even).test(-7)); // true
System.out.println(negative.or(even).test(8)); // true
// NOT negative
Predicate<Integer> nonNegative = negative.negate();
System.out.println(nonNegative.test(0)); // true
}
} Important
Internally and() and or() use the && and || operators, so evaluation is short-circuiting. If a.test(x) returns false, then in a.and(b) the predicate b is never called. That matters when the second predicate is expensive (a database lookup) or when it guards against a NullPointerException.
Why does the signature say Predicate<? super T> instead of Predicate<T>? Because the predicate you pass in may be declared for a more general type. A "not null" check is naturally a Predicate<Object>, and it works perfectly well for strings:
Predicate<Object> notNull = Objects::nonNull;
Predicate<String> longString = s -> s.length() > 5;
Predicate<String> safe = longString.and(notNull); // compiles thanks to ? super T Static methods isEqual() and not()
The interface also ships two static factory methods.
Predicate.isEqual(Object targetRef) (Java 8) returns a predicate that compares its argument with a reference value using Objects.equals(), which means it is null-safe:
Predicate<String> isJava = Predicate.isEqual("Java");
System.out.println(isJava.test("Java")); // true
System.out.println(isJava.test("Kotlin")); // false
System.out.println(isJava.test(null)); // false, no NullPointerException Predicate.not(Predicate<? super T> target) arrived in Java 11. It solves the case where negate() cannot be called on a method reference directly:
import java.util.List;
import java.util.function.Predicate;
public class PredicateNotExample {
public static void main(String[] args) {
List<String> lines = List.of("Java", " ", "Predicate", "");
// String::isBlank.negate() does not compile
List<String> result = lines.stream()
.filter(Predicate.not(String::isBlank))
.toList();
System.out.println(result); // [Java, Predicate]
}
} Predicate in Stream API and removeIf()
The main practical use of Predicate is filtering data. The filter() method of the Stream API accepts exactly a predicate:
import java.util.List;
import java.util.function.Predicate;
public class PredicateStreamExample {
public static void main(String[] args) {
List<Integer> numbers = List.of(-3, 0, 5, 12, -7, 8);
Predicate<Integer> positive = n -> n > 0;
Predicate<Integer> even = n -> n % 2 == 0;
List<Integer> positiveEven = numbers.stream()
.filter(positive.and(even))
.toList();
System.out.println(positiveEven); // [12, 8]
System.out.println(numbers.stream().anyMatch(even)); // true
System.out.println(numbers.stream().allMatch(positive)); // false
System.out.println(numbers.stream().noneMatch(n -> n > 100)); // true
}
} A predicate is also what anyMatch(), allMatch(), noneMatch(), takeWhile() and dropWhile() expect — see the Stream API guide for the full list.
Outside streams, predicates show up in Collection.removeIf():
import java.util.ArrayList;
import java.util.List;
public class RemoveIfExample {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(List.of(1, -2, 3, -4));
numbers.removeIf(n -> n < 0);
System.out.println(numbers); // [1, 3]
}
} Java versions
The terminal operation Stream.toList() requires Java 16, and List.of() requires Java 9. On Java 8 replace them with collect(Collectors.toList()) and Arrays.asList(...) — the Predicate interface itself behaves identically from Java 8 onwards.
BiPredicate, IntPredicate and other specializations
Next to the generic Predicate<T>, the java.util.function package provides variants for two arguments and for primitives: BiPredicate<T, U> checks a pair of values with boolean test(T t, U u), while IntPredicate, LongPredicate and DoublePredicate take an int, long or double directly — avoiding autoboxing and the extra objects it creates.
The other interfaces of the same package have their own lessons: Function, Consumer, Supplier and UnaryOperator.
Where developers get tripped up
1. Treating a predicate as mutable. and(), or() and negate() return a new object. Calling one without assigning the result does nothing at all:
Predicate<Integer> positive = n -> n > 0;
positive.negate(); // result is thrown away
System.out.println(positive.test(5)); // true
Predicate<Integer> nonPositive = positive.negate(); // this is the correct way
System.out.println(nonPositive.test(5)); // false 2. Expecting a NullPointerException at check time. The implementation of and() starts with Objects.requireNonNull(other), so the NullPointerException is thrown while the predicate is being composed, not when test() is finally called:
Predicate<String> p = s -> s.isEmpty();
Predicate<String> combined = p.and(null); // NullPointerException right here 3. Confusing Predicate with Function<T, Boolean>. Formally both answer true or false, but Function<T, Boolean> returns the wrapper object Boolean (extra boxing and a possible null), has no and(), or() or negate(), and cannot be passed to filter() or removeIf().
4. Writing one huge condition instead of composing. Instead of a single sprawling lambda, build the predicate from named parts — the code documents itself:
Predicate<String> notNull = Objects::nonNull;
Predicate<String> notBlank = s -> !s.isBlank();
Predicate<String> startsWithJ = s -> s.startsWith("J");
Predicate<String> valid = notNull.and(notBlank).and(startsWithJ); Tip
In the chain notNull.and(notBlank) the order matters: thanks to short-circuiting the null check has to come first, otherwise s.isBlank() will blow up with a NullPointerException.
The full specification of the interface lives in the official Oracle javadoc.
Frequently asked questions
Can a Java Predicate throw a checked exception?
No. boolean test(T t) declares no throws clause, so a lambda passed to filter() cannot throw a checked exception directly. Either catch it inside the lambda and return a boolean, wrap it in an unchecked exception such as UncheckedIOException, or extract the call into a separate method that handles it. Unchecked exceptions propagate normally out of test().
How do I combine a list of predicates built at runtime?
Reduce the collection with a method reference: filters.stream().reduce(x -> true, Predicate::and) gives the logical AND of every predicate, and reduce(x -> false, Predicate::or) gives the logical OR. The identity value is what an empty list should evaluate to, so pick it deliberately.
Is Predicate.not() available in Java 8?
No, Predicate.not() was added in Java 11. On Java 8 either call negate() on a predicate variable, or write the lambda explicitly, for example s -> !s.isEmpty(). Assigning the method reference to a variable first also works: Predicate<String> blank = String::isEmpty; blank.negate();
Where else does the JDK accept a Predicate besides Stream.filter()?
Common places are Collection.removeIf(), Optional.filter(), Stream.takeWhile() and dropWhile(), Files.find(), Collectors.partitioningBy() and Pattern.asPredicate(), which turns a regular expression into a ready-made string predicate.
Комментарии