4

I'd like to make Java Bean Validation constraints configurable by Spring, possibly by using properties. An example:

class Pizza {

    @MaxGramsOfCheese(max = "${application.pizza.cheese.max-grams}")
    int gramsOfCheese;

}

I haven't been able to get this to work or find much documentation about this.

Is something like this even possible? I know that messages are configurable in a Validationmessages.properties file, so I'm hoping something similar is possible for constraint values.

Dormouse
  • 1,617
  • 1
  • 23
  • 33

2 Answers2

3

For any custom validation, you need to implement a custom validator by implementing the ConstraintValidator interface, and then provide that custom validator to the custom validation annotation that you create.

The custom validator:

public class MaxGramsOfCheeseValidator implements ConstraintValidator<MaxGramsOfCheese, Integer> {

    @Value("${application.pizza.cheese.max-grams}")
    protected int maxValue;

    @Override
    public void initialize(MaxGramsOfCheese constraintAnnotation) {
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return value != null && value <= maxValue;
    }

}

The custom validation annotation:

@Documented
@Constraint(validatedBy = {MaxGramsOfCheeseValidator.class})
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MaxGramsOfCheese {
    String message() default "Some issue here"; //message to be returned on validation failure

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

Using the custom validation annotation:

class Pizza {

    @MaxGramsOfCheese
    int gramsOfCheese;

}

Note that if you want the value for the annotation to be accessed from the properties file, you'll have to provide that in the custom validator as shown.

Madhu Bhat
  • 13,559
  • 2
  • 38
  • 54
3

In addition to @Madhu Bhat you can configure your ConstraintValidator class to read properties from Spring's Environment.

public class MaxGramsOfCheeseValidator implements ConstraintValidator<MaxGramsOfCheese, Integer> {

    @Autowired
    private Environment env;

    private int max;

    public void initialize(MaxGramsOfCheese constraintAnnotation) {
        this.max = Integer.valueOf(env.resolvePlaceholders(constraintAnnotation.max()));
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return value != null && value <= this.max;
    }

}

Thus you can use @MaxGramsOfCheese annotation on different fields with different parameters which may be more appropriate in your case.

class Pizza {

    @MaxGramsOfCheese(max = "${application.pizza.cheddar.max-grams}")
    int gramsOfCheddar;

    @MaxGramsOfCheese(max = "${application.pizza.mozerella.max-grams}")
    int gramsOfMozerella;

}
Yavuz Tas
  • 344
  • 3
  • 16
  • I like this solution, because it keeps max configurable either with a property or with a fixed value (although this needs to be programmed). Unfortunately this doesn't work: `java.lang.NumberFormatException: For input string: "${application.pizza.cheddar.max-grams}"`. – Dormouse Jul 08 '19 at 10:08
  • It is probably caused by you do not have the property definition in your `application.properties` file. You can simply define a default value like `${application.pizza.cheddar.max-grams:5}`. In addition, you can change the method `env.resolvePlaceholders` to `env.resolveRequiredPlaceholders` in the `MaxGramsOfCheeseValidator`. This will force to throw an `IllegalArgumentException` when there is no property found with the key you add to the annotation. Thus you can handle this scenario as you need or just log for a warning. – Yavuz Tas Jul 08 '19 at 11:54
  • I had the property defined in an application.yml. Seems these are not read correctly. When changing to an application.properties it works. – Dormouse Jul 08 '19 at 12:08
  • Glad it works :) It is strange normally it should work. If you have both `.properties` and `.yml` then for the same keys `.properties` file overrides the `.yml` one. Maybe that is the case but I'm not sure. – Yavuz Tas Jul 08 '19 at 12:32
  • What about if I use my own properties file, like myprops.properties? This file is also in resource folder, how to access it via Environment? Or is another solution necessary? – neblaz May 13 '20 at 10:26
  • As I know only the `application-{profile}.properties` files imported automatically. The others like as you said for `myprops.properties` you need to import it manually to your Spring's configuration file. Ex: `@PropertySource("classpath:myprops.properties")` – Yavuz Tas May 13 '20 at 11:29
  • Then, you can access these properties by using Spring's `Environment` as I guess. – Yavuz Tas May 13 '20 at 11:30