Complex, multi-field validation rules in Spring Boot are implemented with a custom class-level constraint annotation backed by a ConstraintValidator that checks the relationship between two or more fields at once.
Key Points: • Define a custom annotation (e.g. @FieldsMatch) with @Target(TYPE) since it validates the whole object, not a single field. • Implement ConstraintValidator<FieldsMatch, YourDto> and put the cross-field comparison logic inside isValid(). • Apply the annotation directly on the DTO class, and it runs automatically when the controller method uses @Valid. • This keeps the validation logic reusable and testable independently of the controller. • For simpler cross-field cases, a manual check inside the service layer is sometimes more pragmatic than building a full custom annotation.
Example: A password-reset form needs password and confirmPassword to match -- a class-level @FieldsMatch annotation on the DTO checks both fields together and rejects the request with a validation error if they differ.
Code Example:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FieldsMatchValidator.class)
public @interface FieldsMatch { String message() default "Fields do not match"; ... }
public class FieldsMatchValidator implements ConstraintValidator<FieldsMatch, PasswordResetDto> {
public boolean isValid(PasswordResetDto dto, ConstraintValidatorContext ctx) {
return dto.getPassword().equals(dto.getConfirmPassword());
}
}Interview Tip: A concise interview answer is:
"I'd create a custom class-level annotation with a ConstraintValidator that compares the relevant fields together, then apply it on the DTO so it's checked automatically wherever @Valid is used. That keeps the cross-field rule encapsulated and reusable instead of scattering manual checks across controllers."