Implementing Cross-Field Validation with Hibernate Validator: Methods and Best Practices

Dec 02, 2025 · Programming · 16 views · 7.8

Keywords: Hibernate Validator | Cross-Field Validation | JSR-303

Abstract: This article provides an in-depth exploration of two primary methods for implementing cross-field validation in Hibernate Validator 4.x. It details the class-level annotation approach using FieldMatch validators, covering custom annotation definition, validator implementation, and practical application in form validation. Additionally, it presents the simplified @AssertTrue annotation method as an alternative. Through comparative analysis of both approaches' strengths and limitations, the article offers guidance for developers in selecting appropriate solutions for different scenarios, emphasizing adherence to JSR-303 specifications.

The Need for Cross-Field Validation

In practical Java Bean Validation (JSR-303) applications, developers frequently encounter scenarios requiring validation of relationships between multiple fields. Typical examples include password confirmation, email address matching, date range checking, and similar interdependent validations that cannot be satisfied by individual field validators.

Standard Implementation with Class-Level Annotations

Following JSR-303 best practices, cross-field validation should be implemented at the class level rather than having one field's annotation validate another. This approach maintains clarity and maintainability of validation logic.

Custom Annotation Definition

First, define a class-level annotation @FieldMatch:

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = FieldMatchValidator.class)
@Documented
public @interface FieldMatch {
    String message() default "{constraints.fieldmatch}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    String first();
    String second();
    
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        FieldMatch[] value();
    }
}

Validator Implementation

The validator class must implement the ConstraintValidator<FieldMatch, Object> interface:

public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
    private String firstFieldName;
    private String secondFieldName;
    
    @Override
    public void initialize(final FieldMatch constraintAnnotation) {
        firstFieldName = constraintAnnotation.first();
        secondFieldName = constraintAnnotation.second();
    }
    
    @Override
    public boolean isValid(final Object value, final ConstraintValidatorContext context) {
        try {
            final Object firstObj = BeanUtils.getProperty(value, firstFieldName);
            final Object secondObj = BeanUtils.getProperty(value, secondFieldName);
            return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
        } catch (final Exception ignore) {
            return true;
        }
    }
}

Practical Application Example

Application in user registration forms:

@FieldMatch.List({
    @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
    @FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")
})
public class UserRegistrationForm {
    @NotNull
    @Size(min=8, max=25)
    private String password;
    
    @NotNull
    @Size(min=8, max=25)
    private String confirmPassword;
    
    @NotNull
    @Email
    private String email;
    
    @NotNull
    @Email
    private String confirmEmail;
}

Simplified Implementation Alternative

As a supplementary approach, the @AssertTrue annotation provides a simpler method for cross-field validation:

public class MyBean {
    @Size(min=6, max=50)
    private String pass;
    private String passVerify;
    
    @AssertTrue(message = "Fields `pass` and `passVerify` should be equal")
    private boolean isValidPass() {
        return Objects.equals(pass, passVerify);
    }
}

Comparative Analysis and Selection Guidance

Both approaches have distinct advantages and limitations:

Recommend using the class-level annotation approach when validation logic needs reuse or multiple field pairs require validation, while the @AssertTrue approach is suitable for simpler scenarios.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.