[JAVA] How to pass a value other than annotation attribute to Hibernate Validator (Bean Validation) message (embed method)

You can embed the attribute value specified in the constraint annotation in the Bean Validation error message as follows. (Note that the content handled in this entry is premised on using Hibernate Validator)

Constraint annotation specification example


@Size(min = 4, max = 16)
private String foo;

Validation execution example


// ...
Bean bean = new Bean();
bean.foo = "123";
Set<ConstraintViolation<Bean>> violations = vb.validate(bean);
// ...

The error message generated in the above case would be "size must be between 4 and 16".

The background that I wanted to embed other than the annotation attribute value is ...

In the case I'm working on now, I was forced to get the reference value to be referred to when checking the input from the external setting (actually the property file). There was an option to check with logic without using Bean Validation, but I wanted to use the Bean Validation mechanism as much as possible for input checking, so I investigated how to implement it with Bean Validation.

Specifically, what kind of constraint annotation did you make?

Although it was not actually created, I decided to create an annotation that checks "the date is after the specified number of days" and a constraint annotation that gets the "specified number of days" part from the property file. ..

Example of creating constraint annotation


@DaysLater(days = "foo.daysLater", defaultValue = 5)
private LocalDate fooDate;

Property file setting example


foo.daysLater=10

src/main/resources/ValidationMessages.Properties setting example


com.example.validation.DaysLater.message = must be {days} days later

I was able to check it, but the message is disappointing ...

I'd like to say "must be 10 days later" as the error message when the constraint is violated ... Unfortunately, the default behavior is "must be foo.daysLater days later". Well, it's a natural result ...

Is it possible to comply with the Bean Validation specifications?

I haven't investigated it properly, but when I looked at the specifications, there seemed to be no way to deal with it within the Bean Validation specifications. However, since the EL formula can be used when assembling the message, it may be possible to deal with it by mastering the EL formula.

You can do this by using the Hibernate Validator extension!

When using Extensions provided by Hibernate Validation, the message You can change the values that can be referenced from the definition.


private int value; //Store property value from annotation attribute value (property key)

// ...

public boolean isValid(LocalDate targetValue, ConstraintValidatorContext context) {

  boolean result = //Check contents omitted...
  
    //If the check is NG, the default behavior is invalidated and arbitrary constraint violation information is generated.
  if (!result) {
    //Convert to the interface provided by Hibernate Validator to use the extension of Hibernate Validator
    HibernateConstraintValidatorContext hibernateContext =
        context.unwrap(HibernateConstraintValidatorContext.class);

    //Disables the generation of default constraint violation information
    hibernateContext.disableDefaultConstraintViolation();

    //Generate arbitrary constraint violation information
    hibernateContext
        //In the message definition{value}To be able to refer to the value with
        .addMessageParameter("days", this.days)
        //Allow the value to be referenced by the variable name value in the EL expression in the message definition
        .addExpressionVariable("days", this.days)
        //Use the template specified in the annotation for the message template (same as the default behavior)
        .buildConstraintViolationWithTemplate(hibernateContext.getDefaultConstraintMessageTemplate())
        //Add constraint violation information
        .addConstraintViolation();
  }
  return result;
}

With the above implementation, it is possible to embed the value corresponding to the property key instead of the property key in the message. By utilizing this mechanism, it is also possible to embed a "date when the check is OK" in the message.

public boolean isValid(LocalDate targetValue, ConstraintValidatorContext context) {

  LocalDate validMinDate = LocalDate.now().plusDays(value);
  boolean result = //Check contents omitted...
  
  if (!result) {
    HibernateConstraintValidatorContext hibernateContext =
        context.unwrap(HibernateConstraintValidatorContext.class);
    hibernateContext.disableDefaultConstraintViolation();
    hibernateContext
        .addMessageParameter("days", this.days)
        .addExpressionVariable("days", this.days)
        //Added the date when the check is OK
        .addMessageParameter("validMinDate", validMinDate)
        .addExpressionVariable("validMinDate", validMinDate)
        .buildConstraintViolationWithTemplate(hibernateContext.getDefaultConstraintMessageTemplate())
        .addConstraintViolation();
  }
  return result;
}

src/main/resources/ValidationMessages.properties


com.example.validation.DaysLater.message = must be {validMinDate} or later

The error message when the constraint is violated is "must be 2018-09-26 or later", which can be a more intuitive message.

Summary

If running on Hibernate Validator is a prerequisite, using the extensions introduced here will make it easier to compose messages with high usability. The project I'm currently working on can be executed on the Hivernate Validator, so I'm thinking of making effective use of this mechanism.

Recommended Posts

How to pass a value other than annotation attribute to Hibernate Validator (Bean Validation) message (embed method)
How to create a method
[Swift5] How to communicate from ViewController to Model and pass a value
How to pass the value to another screen