W3docs

Java Predicate Interface

Test conditions on values in Java with the Predicate functional interface and its and/or/negate combinators.

Java Predicate Interface

Predicate<T> is the functional interface for the question "is this value good?" — one input of type T, one boolean answer. It sits at the heart of Stream.filter, Collection.removeIf, Optional.filter, and every JDK method that says "keep the ones that match." The interface is tiny — a single test(T) method — but it ships with a small combinator algebra (and, or, negate, isEqual, not) that lets you build complex conditions from simple ones without ever writing the boolean glue by hand.

This chapter is the same shape as the rest of Part 12's interface zoom-ins: the interface, its three or four useful methods, the algebra, then a worked example.

The interface

The whole declaration, paraphrased:

@FunctionalInterface
public interface Predicate<T> {
  boolean test(T t);                         // the only abstract method

  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 target);
  static  <T> Predicate<T> not(Predicate<? super T> target);   // Java 11+
}

test is the single abstract method that lambdas and method references implement. Everything else is built on top of it. You'll rarely call test yourself — stream().filter(...) and list.removeIf(...) call it for you — but knowing the method name matters when you write code that accepts a Predicate<T> and needs to invoke it.

Predicate<String> notBlank = s -> !s.isBlank();
boolean ok = notBlank.test("hello");          // true

and, or, negate — boolean algebra without the glue

The three default methods compose predicates the same way the &&, ||, ! operators compose booleans:

Predicate<String> notNull  = Objects::nonNull;
Predicate<String> notBlank = s -> !s.isBlank();
Predicate<String> longEnough = s -> s.length() >= 3;

Predicate<String> useful   = notNull.and(notBlank).and(longEnough);
Predicate<String> usableOrShort = useful.or(s -> s.length() == 1);
Predicate<String> bad      = useful.negate();

Two properties matter:

  • Short-circuit, in declaration order. a.and(b) only calls b.test when a.test returned true. a.or(b) only calls b.test when a.test returned false. That's the same evaluation order as && and ||, which means you can put cheap, common-failing checks first and the expensive ones last.
  • Each call returns a new Predicate. The combinators don't mutate this. Reuse the originals as much as you like.

negate() simply flips the result. useful.negate() returns true for nulls, blanks, and strings shorter than 3 — every case useful rejected.

Predicate.not — the readable negation

Java 11 added a static shorthand:

list.removeIf(Predicate.not(String::isBlank));    // remove every blank string

Predicate.not(p) is the same boolean answer as p.negate(), but it composes much more naturally at the call site. The method-reference form String::isBlank is a Predicate<String> on its own — but you can't write (String::isBlank).negate(), because the compiler needs a target type before it can resolve the reference. Predicate.not(String::isBlank) gives it that target type, and the whole thing reads as "not blank" in English order.

A static import of Predicate.not makes filter chains even cleaner:

import static java.util.function.Predicate.not;
...
var nonBlank = lines.stream().filter(not(String::isBlank)).toList();

Predicate.isEqual — null-safe equality

Predicate<Object> isFoo = Predicate.isEqual("foo");        // o -> Objects.equals(o, "foo")

The implementation is literally t -> Objects.equals(target, t), which means null on either side compares safely. It rarely saves keystrokes over s -> s.equals("foo"), but it does save you when the stream may contain nullnull.equals("foo") would NPE, Objects.equals(null, "foo") returns false.

Where Predicate<T> shows up in the JDK

The same Predicate<T> flows through every "filter" API:

Stream<String> kept = stream.filter(notBlank);             // Stream.filter
boolean removed     = list.removeIf(String::isBlank);      // Collection.removeIf
Optional<String> ok = opt.filter(notBlank);                 // Optional.filter
boolean any         = stream.anyMatch(notBlank);            // anyMatch / allMatch / noneMatch
Map<K, V> small     = map.values().retainAll(p);            // (via Collection.retainAll on entry set)

Every one of these takes the same shape, so a Predicate<T> you build once is reusable in every direction — and assembling it from and/or/negate is exactly how you avoid the "I have three slightly different filters, all near-duplicate" smell.

Primitive specialisations — IntPredicate, LongPredicate, DoublePredicate

Predicate<Integer> works on ints, but each call boxes the input. For tight numeric pipelines the package ships:

IntPredicate    even = n -> n % 2 == 0;
LongPredicate   big  = n -> n > 1_000_000_000L;
DoublePredicate hot  = d -> d > 37.5;

Same and/or/negate algebra, no boxing. These are what IntStream.filter accepts — using Predicate<Integer> there would force the stream to autobox every element on the way in.

BiPredicate<T, U> — two-argument tests

When the question takes two inputs (a key and a value, a row and a column, an old and a new), use BiPredicate:

BiPredicate<String, Integer> longEnoughFor = (s, n) -> s.length() >= n;
boolean ok = longEnoughFor.test("hello", 4);             // true

The combinator surface is smaller — and, or, negate exist, but there's no two-argument isEqual or not. Map.removeIf((k, v) -> ...) is exactly a BiPredicate<K, V>.

A worked example: predicates, composition, the algebra, and where they plug in

The program below builds three small predicates over User, composes them with and/or/negate, demonstrates short-circuiting by counting calls, swaps in Predicate.not for negation at a removeIf call site, and uses an IntPredicate against an IntStream to show the primitive variant.

java— editable, runs on the server

What to take from the run:

  • The three building-block predicates (adult, active, namedWell) stayed reusable. eligible, minor, and reachable were built by composition rather than by writing three separate lambdas with overlapping logic.
  • and short-circuited exactly the way && does: expensive ran fewer times than cheap because every minor was rejected before the expensive check fired. That's the lever you have for ordering — put cheap, frequently-failing checks first.
  • Predicate.not(...) at the removeIf call site read as plain English ("remove if not non-blank") and avoided needing a target type before negation. Static-importing not is the small finishing touch.
  • Predicate.isEqual("foo") counted the two "foo" entries past a null without throwing. s -> s.equals("foo") would have NPE'd on the null element.
  • IntPredicate even = n -> n % 2 == 0; plugged straight into IntStream.filter without boxing — and the same .and(...) combinator works on the primitive specialisation.

What's next

Predicate<T> answers yes-or-no. The next chapter, Java Function Interface, covers the interface for the other half of stream work: transforming one value into another. The shape — single-method, default-method composition (andThen, compose, plus the identity() static) — is the same as Predicate, and the same lessons about ordering, reuse, and primitive specialisations carry across.

Practice

Practice

You have `Predicate<String> notNull = Objects::nonNull;` and `Predicate<String> notBlank = s -> !s.isBlank();` and want one predicate that returns `true` only when the string is both non-null and non-blank, with `notNull` checked *first* so `notBlank` never runs on a `null`. Which expression does this correctly?