Back to results for “Functional Interfaces & Method References”
Java › Functional Interfaces & Method References

How would you design a fluent, chainable validation API using functional interfaces, similar to how Comparator's thenComparing() works, but for validation rules that accumulate error messages rather than short-circuiting on the first failure?

Advanced

Answer

Unlike Predicate's and()/or() (which short-circuit and only report pass/fail), a validation-accumulating design typically defines a custom functional interface returning a richer result type (like a List<String> of error messages, or a ValidationResult record) rather than a plain boolean, with a custom 'combine' default method that runs both validators regardless of the first one's outcome and merges their respective error lists together -- this lets you collect ALL validation failures for comprehensive error reporting (valuable for user-facing form validation, where showing every problem at once is more helpful than only the first one encountered) rather than Predicate's short-circuiting all-or-nothing boolean result.
@FunctionalInterface
interface Validator<T> {
    List<String> validate(T input);

    default Validator<T> and(Validator<T> other) {
        return input -> {
            List<String> errors = new ArrayList<>(this.validate(input));
            errors.addAll(other.validate(input));  // accumulates BOTH, doesn't short-circuit
            return errors;
        };
    }
}

Validator<String> notEmpty = s -> s.isEmpty() ? List.of("must not be empty") : List.of();
Validator<String> maxLength = s -> s.length() > 20 ? List.of("too long") : List.of();
Validator<String> combined = notEmpty.and(maxLength);
System.out.println(combined.validate(""));  // reports BOTH applicable errors if both rules fail
Real-world example A registration form's custom Validator-based composition reports every validation rule that failed for a given field simultaneously (empty AND too long, if both apply), giving the user complete feedback in one pass, a meaningfully better user experience than Predicate's short-circuiting and()/or() would provide for this specific accumulating use case.

Common follow-ups: What's the performance trade-off of always running every validator (no short-circuiting) versus Predicate's short-circuit behavior?;How would you extend this design to also support conditional validators that only apply under certain circumstances?