This library unlocks powerful cross-field validation for your Java applications, enabling you to define complex constraints that span multiple fields within an object.
Jakarta Bean Validation is great for single-field validation, but it falls short when you need to enforce rules that involve relationships between different fields. This library bridges that gap, providing a flexible and intuitive way to define and apply cross-field validation.
This solution overcomes the limitations of ConstraintValidatorContext, which doesn't allow interference with the
object context when writing field-level validators. There are long-standing open issues on this topic:
Advantages of this library:
- More flexible validations: Define complex validation logic involving multiple fields.
- Improved readability: Create custom annotations that resemble built-in constraints like @NotNulland@NotEmpty.
- Simplified validation: Use a single @EnableCrossFieldConstraintsannotation to enable all custom validators for a class.
1. Add the Dependency
<dependency>
    <groupId>io.github.maharramoff</groupId>
    <artifactId>cross-field-validation</artifactId>
    <version>1.3.0</version>
</dependency>2. Annotate your class with @EnableCrossFieldConstraints
@EnableCrossFieldConstraints
public class SignupRequestDTO
{
    private String username;
    private String password;
    @MatchWith("password")
    private String confirmPassword;
}3. Implement a custom validator
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@CrossFieldConstraint(validatedBy = MatchWithValidator.class)
public @interface MatchWith
{
    String field();
    String message() default "Fields do not match.";
}
public class MatchWithValidator extends BaseCrossFieldValidator
{
    @Override
    public boolean isValid(Object obj, Map<Class<?>, List<Field>> fieldMapping, List<CrossFieldConstraintViolation> violations)
    {
        processFields(obj, fieldMapping, MatchWith.class, (field, annotation) ->
        {
            Object fieldValue      = getProperty(obj, field.getName());
            Object otherFieldValue = getProperty(obj, annotation.field());
            if (fieldValue == null && otherFieldValue == null)
            {
                return; // Both null is considered valid
            }
            if (fieldValue == null || !fieldValue.equals(otherFieldValue))
            {
                violations.add(new CrossFieldConstraintViolation(field.getName(), annotation.message()));
            }
        });
        return violations.isEmpty();
    }
}This library utilizes a ConstraintValidator to manage cross-field validation. Custom validators implement the CrossFieldConstraintValidator interface, providing the logic for your specific constraints.
1. Annotation Processing: When the validation framework encounters the @EnableCrossFieldConstraints annotation on
a class, it triggers the CrossFieldValidationProcessor.
2. Validator Execution: The CrossFieldValidationProcessor iterates through
registered CrossFieldConstraintValidator implementations.
3. Field Analysis: Each validator analyzes the fields of the object, looking for its corresponding annotation (
e.g., @MatchWith).
4. Validation Logic: If the annotation is present, the validator executes its custom validation logic, comparing field values as needed.
5. Violation Reporting: If a constraint is violated, the validator adds a CrossFieldConstraintViolation to the
list, which
is then handled by the validation framework.
Contributions are welcome! Please fork the repository and submit a pull request with your changes. Ensure your code follows the existing code style and includes appropriate unit tests.