I just tested it with Spring Boot 2.2 and I notice the same thing.
Now the fact that the method is invoked one, twice or even more by the Hibernate Validator implementation is something that you should not even considered.
Just for the record, it is invoked the first time before processing to the validation to check that the validation is required.
If the validation is required, the constraint is processed and so read a second time : so the method that bears the annotation is invoked a second time.
Here the method with my 3 comments to explain the flow :
private boolean validateMetaConstraint(ValidationContext<?> validationContext, ValueContext<?, Object> valueContext, Object parent, MetaConstraint<?> metaConstraint) {
// .... FIRST INVOCATION
if ( isValidationRequired( validationContext, valueContext, metaConstraint ) ) {
if ( parent != null ) {
// .... SECOND INVOCATION with valueContext.getValue()
valueContext.setCurrentValidatedValue( valueContext.getValue( parent, metaConstraint.getLocation() ) );
}
success = metaConstraint.validateConstraint( validationContext, valueContext );
validationContext.markConstraintProcessed( valueContext.getCurrentBean(), valueContext.getPropertyPath(), metaConstraint );
}
// ....
}
That behavior comes from the ValueContext class which stores validation information and that may be invoked multiple times for optimization or processing reasons :
An instance of this class is used to collect all the relevant
information for validating a single class, property or method
invocation.
Still forget that, it is a detail of implementation, tomorrow in the next version the annotated method could be invoked a single time and it should never break your logic. So just don't rely on it.
Which matters is that the API respects its contract and it does it : a single validation error is returned even if the method is invoked twice by the implementation.
public class DemoObj {
private final boolean value;
DemoObj(boolean value){
this.value = value;
}
@AssertTrue(message = "My business rule was failed")
public boolean isMyBusinessRule() {
System.out.println("isMyBusinessRule called");
return value;
}
}
Use it :
Set<ConstraintViolation<DemoObj>> constraintViolations = validator.validate(new DemoObj (true));
System.out.println("validations errors : " + constraintViolations.size());
constraintViolations = validator.validate(new DemoObj (false));
System.out.println("validation errors : " + constraintViolations.size());
Output :
isMyBusinessRule called
isMyBusinessRule called
validations errors : 0
isMyBusinessRule called
isMyBusinessRule called
validation errors : 1
isMyBusinessRule()? You say that your problem is that this method is being called twice, but you don't show us why it would be called even once. Please provide a Minimal, Complete, and Verifiable Example.