Predicate
Predicate<T> is a functional interface in java.util.function representing a boolean-valued function of one argument, with the single abstract method boolean test(T t). It is one of the four foundational functional interfaces in the Java standard library and is used throughout the Collections framework, Streams API, and Optional for filtering, condition testing, and validation. Predicate is designed for composition: its default methods and(Predicate), or(Predicate), and negate() allow building complex boolean expressions from simple predicates without boilerplate. The static methods isEqual(Object) and not(Predicate) provide factory methods for common cases. The primitive specializations IntPredicate, LongPredicate, and DoublePredicate avoid boxing overhead for numeric values. BiPredicate<T,U> extends the concept to two-argument boolean functions. This entry covers the complete Predicate API, all composition methods and their short-circuit semantics, the static factory methods, primitive specializations, BiPredicate, using Predicate in stream pipelines and Collections methods, building validation frameworks with Predicate composition, and the performance and readability trade-offs of different composition styles.
Predicate API — test(), and(), or(), negate(), and Static Methods
// ── Basic Predicate creation ──────────────────────────────────────────
Predicate<String> notBlank = s -> !s.isBlank();
Predicate<String> shortEnough = s -> s.length() <= 100;
Predicate<String> startsUpper = s -> Character.isUpperCase(s.charAt(0));
Predicate<Integer> positive = n -> n > 0;
Predicate<Integer> even = n -> n % 2 == 0;
System.out.println(notBlank.test("hello")); // true
System.out.println(notBlank.test(" ")); // false
System.out.println(positive.test(-5)); // false
System.out.println(even.test(4)); // true
// ── Method references as Predicates ───────────────────────────────────
Predicate<String> isEmpty = String::isEmpty; // T → boolean
Predicate<Object> isNull = Objects::isNull;
Predicate<Object> nonNull = Objects::nonNull;
Predicate<String> matches = s -> s.matches("[A-Z][a-z]+");
// ── and() — short-circuit AND ─────────────────────────────────────────
Predicate<String> validInput = notBlank.and(shortEnough).and(startsUpper);
System.out.println(validInput.test("Hello")); // true
System.out.println(validInput.test("hello")); // false (doesn't start uppercase)
System.out.println(validInput.test("")); // false (blank, short-circuits here)
System.out.println(validInput.test("H".repeat(200))); // false (too long)
// Short-circuit: if notBlank returns false, shortEnough is NOT called:
Predicate<String> sideEffectAnd = notBlank.and(s -> {
System.out.println(" Checking length of: " + s);
return s.length() <= 5;
});
sideEffectAnd.test(""); // false — no "Checking length" printed (short-circuited)
sideEffectAnd.test("hi"); // true — "Checking length" printed (and() evaluated)
// ── or() — short-circuit OR ───────────────────────────────────────────
Predicate<Integer> positiveOrEven = positive.or(even);
System.out.println(positiveOrEven.test(3)); // true (positive, even not checked)
System.out.println(positiveOrEven.test(-4)); // true (not positive, but even)
System.out.println(positiveOrEven.test(-3)); // false (neither)
// ── negate() ─────────────────────────────────────────────────────────
Predicate<Integer> notPositive = positive.negate();
Predicate<String> notEmpty = isEmpty.negate();
System.out.println(notPositive.test(5)); // false
System.out.println(notPositive.test(-3)); // true
System.out.println(notEmpty.test("hi")); // true
System.out.println(notEmpty.test("")); // false
// ── Predicate.not() — Java 11+, for readable negation of method refs ──
// Before Java 11 — ugly:
Predicate<String> notBlank1 = s -> !s.isBlank();
// Java 11+ — cleaner:
Predicate<String> notBlank2 = Predicate.not(String::isBlank);
// In stream pipelines this is most readable:
List<String> lines = List.of("hello", " ", "world", "", "java");
List<String> nonBlanks = lines.stream()
.filter(Predicate.not(String::isBlank)) // cleaner than s -> !s.isBlank()
.collect(Collectors.toList());
System.out.println(nonBlanks); // [hello, world, java]
// ── Predicate.isEqual() — equality testing ────────────────────────────
Predicate<String> isHello = Predicate.isEqual("hello");
System.out.println(isHello.test("hello")); // true
System.out.println(isHello.test("world")); // false
System.out.println(isHello.test(null)); // false (Objects.equals handles null)
// Useful for filtering a specific value:
List<String> words = List.of("hello", "world", "hello", "java");
long helloCount = words.stream().filter(Predicate.isEqual("hello")).count();
System.out.println(helloCount); // 2Predicate in Streams, Collections, and Validation Frameworks
// ── Predicate in Stream.filter() ──────────────────────────────────────
List<Integer> numbers = List.of(1, -2, 3, -4, 5, -6, 7, 8, -9, 10);
// Single predicate:
List<Integer> positives = numbers.stream()
.filter(n -> n > 0)
.collect(Collectors.toList());
System.out.println(positives); // [1, 3, 5, 7, 8, 10]
// Composed predicate:
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEven = n -> n % 2 == 0;
List<Integer> positiveEvens = numbers.stream()
.filter(isPositive.and(isEven))
.collect(Collectors.toList());
System.out.println(positiveEvens); // [8, 10]
// Negate to get non-positives:
List<Integer> nonPositives = numbers.stream()
.filter(isPositive.negate())
.collect(Collectors.toList());
System.out.println(nonPositives); // [-2, -4, -6, -9]
// ── Collection.removeIf() ─────────────────────────────────────────────
List<String> mutableList = new ArrayList<>(
List.of("apple", "", "banana", " ", "cherry", "date")
);
mutableList.removeIf(String::isBlank); // removes "" and " "
System.out.println(mutableList); // [apple, banana, cherry, date]
// removeIf with composed predicate:
mutableList.removeIf(s -> s.length() <= 4); // removes "date"
System.out.println(mutableList); // [apple, banana, cherry]
// ── Optional.filter() ─────────────────────────────────────────────────
Optional<String> opt1 = Optional.of("hello").filter(s -> s.length() > 3);
Optional<String> opt2 = Optional.of("hi").filter(s -> s.length() > 3);
System.out.println(opt1); // Optional[hello]
System.out.println(opt2); // Optional.empty
// Chained Optional filter + map:
Optional<Integer> result = Optional.of(" hello ")
.map(String::trim)
.filter(Predicate.not(String::isEmpty))
.map(String::length);
System.out.println(result); // Optional[5]
// ── Validation framework with Predicate composition ────────────────────
record ValidationRule<T>(Predicate<T> condition, String errorMessage) {
boolean check(T value) { return condition.test(value); }
}
class Validator<T> {
private final List<ValidationRule<T>> rules = new ArrayList<>();
Validator<T> require(Predicate<T> condition, String message) {
rules.add(new ValidationRule<>(condition, message));
return this; // fluent API
}
List<String> validate(T value) {
return rules.stream()
.filter(rule -> !rule.check(value))
.map(ValidationRule::errorMessage)
.collect(Collectors.toList());
}
boolean isValid(T value) { return validate(value).isEmpty(); }
}
// Build a validator for email strings:
Validator<String> emailValidator = new Validator<String>()
.require(Predicate.not(String::isBlank), "Email cannot be blank")
.require(s -> s.contains("@"), "Email must contain @")
.require(s -> s.indexOf("@") > 0, "Email must have local part")
.require(s -> s.lastIndexOf(".") > s.indexOf("@"), "Email must have domain")
.require(s -> s.length() <= 254, "Email too long");
System.out.println(emailValidator.validate(""));
// [Email cannot be blank, Email must contain @, Email must have local part, ...]
System.out.println(emailValidator.validate("user@example.com"));
// [] (no errors — valid email)
// Reusable predicate building blocks:
Predicate<String> hasMinLength = s -> s.length() >= 8;
Predicate<String> hasUppercase = s -> s.chars().anyMatch(Character::isUpperCase);
Predicate<String> hasDigit = s -> s.chars().anyMatch(Character::isDigit);
Predicate<String> hasSpecial = s -> s.chars().anyMatch(c -> !Character.isLetterOrDigit(c));
Predicate<String> strongPassword = hasMinLength
.and(hasUppercase)
.and(hasDigit)
.and(hasSpecial);
System.out.println(strongPassword.test("Secret1!")); // true
System.out.println(strongPassword.test("password")); // false (no upper, digit, special)BiPredicate, Primitive Specializations, and Advanced Patterns
// ── BiPredicate<T,U> ────────────────────────────────────────────────
BiPredicate<String, String> startsWith = String::startsWith;
BiPredicate<String, String> endsWith = String::endsWith;
BiPredicate<String, String> contains = String::contains;
BiPredicate<Object, Object> equals = Objects::equals;
System.out.println(startsWith.test("hello world", "hello")); // true
System.out.println(endsWith.test("hello world", "world")); // true
System.out.println(equals.test("foo", "foo")); // true
System.out.println(equals.test("foo", null)); // false
// Composition:
BiPredicate<String, String> startsAndEnds =
startsWith.and((s, t) -> s.endsWith(t)); // same prefix = suffix
System.out.println(startsAndEnds.test("abcabc", "abc")); // true
System.out.println(startsAndEnds.test("abcdef", "abc")); // false
// Custom BiPredicate for range checking:
BiPredicate<Integer, Integer> withinRange = (value, limit) ->
Math.abs(value) <= limit;
System.out.println(withinRange.test(5, 10)); // true
System.out.println(withinRange.test(15, 10)); // false
// ── IntPredicate, LongPredicate, DoublePredicate ──────────────────────
IntPredicate positive = n -> n > 0;
IntPredicate even = n -> n % 2 == 0;
IntPredicate positiveEven = positive.and(even);
System.out.println(positiveEven.test(4)); // true (no boxing)
System.out.println(positiveEven.test(-4)); // false
System.out.println(positiveEven.test(3)); // false
// In IntStream pipeline — no boxing:
int[] numbers = IntStream.rangeClosed(-10, 10).toArray();
int[] filtered = Arrays.stream(numbers)
.filter(positive.and(even)) // IntPredicate — no Integer boxing
.toArray();
System.out.println(Arrays.toString(filtered)); // [2, 4, 6, 8, 10]
// LongPredicate for large values:
LongPredicate isLeapYear = year ->
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
System.out.println(isLeapYear.test(2024L)); // true
System.out.println(isLeapYear.test(1900L)); // false (divisible by 100 but not 400)
System.out.println(isLeapYear.test(2000L)); // true (divisible by 400)
// DoublePredicate:
DoublePredicate isFinitePositive = d -> Double.isFinite(d) && d > 0;
System.out.println(isFinitePositive.test(3.14)); // true
System.out.println(isFinitePositive.test(Double.NaN)); // false
System.out.println(isFinitePositive.test(Double.POSITIVE_INFINITY)); // false
// ── Pattern.asPredicate() and asMatchPredicate() ──────────────────────
Pattern emailPattern = Pattern.compile("[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}");
Predicate<String> isValidEmail = emailPattern.asMatchPredicate(); // full match
List<String> candidates = List.of(
"user@example.com", "invalid", "also@valid.org", "@nolocal.com", "no@"
);
List<String> validEmails = candidates.stream()
.filter(isValidEmail)
.collect(Collectors.toList());
System.out.println(validEmails); // [user@example.com, also@valid.org]
// asPredicate vs asMatchPredicate:
Pattern digits = Pattern.compile("\d+");
Predicate<String> containsDigits = digits.asPredicate(); // find() — subsequence
Predicate<String> isAllDigits = digits.asMatchPredicate(); // matches() — full string
System.out.println(containsDigits.test("abc123def")); // true (contains digits)
System.out.println(isAllDigits.test("abc123def")); // false (not all digits)
System.out.println(isAllDigits.test("12345")); // true
// ── Stream.takeWhile(Predicate) — Java 9+ ────────────────────────────
List<Integer> sorted = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Take elements while they are < 5, stop at first failure:
List<Integer> leading = sorted.stream()
.takeWhile(n -> n < 5)
.collect(Collectors.toList());
System.out.println(leading); // [1, 2, 3, 4]
// Stream.dropWhile(Predicate) — Java 9+ — complement:
List<Integer> trailing = sorted.stream()
.dropWhile(n -> n < 5)
.collect(Collectors.toList());
System.out.println(trailing); // [5, 6, 7, 8, 9, 10]
// Practical: process log lines up to a section boundary:
List<String> logLines = Files.readAllLines(Path.of("app.log"));
List<String> untilError = logLines.stream()
.takeWhile(Predicate.not(l -> l.startsWith("ERROR:")))
.collect(Collectors.toList());