2

In a Spring Boot project, I need to validate Business Rules and I'm trying to do this by using Bean Validation.

I wrote a separated class to put my business rules and I implemented by using of "Return value constraints" technique. But, the Validator.validate() method is calling my constraint method twice.

Why? And how to solve this?

The simple code below show more easily to understand the problem:

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
    @Autowired
    private Validator validator;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        DemoObject obj = new DemoObject();
        validator.validate(obj);
    }
}

class DemoObject {
    @AssertTrue(message="My business rule was failed")
    public boolean isMyBusinessRule() {
        System.out.println("isMyBusinessRule called");
        // ... my business rule validation code ...
        return true;
    }
}

The method isMyBusinessRule() was being called twice. The output console shows:

isMyBusinessRule called
isMyBusinessRule called

How to solve this?

4
  • what does the validator class contain. Commented Jun 28, 2019 at 18:41
  • Where is the definition of the Validator bean? Commented Jun 28, 2019 at 18:45
  • What is calling 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. Commented Jun 28, 2019 at 18:54
  • This example is complete. All of the code is this. The Validator is injected by Spring. Then, I call validator.validate() and the object method is called twice! Commented Jun 28, 2019 at 20:16

1 Answer 1

2

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
Sign up to request clarification or add additional context in comments.

8 Comments

But... is there any solution for the problem?
But why do you think that it is a problem ? Which matters is that the contract is respected in terms of API I updated to illustrate.
Is a problem because the validation method (isMyBusinessRule()) may be do some hard work, or take a long time in real situation). I would use the Bean Validation to validate my business rules. Not only to validate entities.
davidxxx, I don't want select if the object will be validated or not. The object always be need validated. Your solution doesn't fit to me. The problem is validator calling twice the method. In documentation of Hibernate Validator does have this example. it was to work
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.