13

I have the following field annotated with @Value, specifying a default value:

@Value("${tolerance.percentage:25}")
private int tolerance;

That code correctly initializes the value of the field to the system property "tolerance.percentage" if that prop exists. If it doesn't exist, it defaults to 25.

I want to go one step further, though, by enforcing a min and max on this int field, since it represents a percentage less than 100 as a whole number, and Murphy's law means someone (probably me) can externally misconfigure the property and my app would start doing weird things at runtime, which is way too late for my liking. I would like an error to be thrown if the property is set to "101" or "-1" upon application startup. Heck, I'd even like for an error to be thrown if I try to default it to 101 in the @Value annotation, but that's not important for the purposes of this question. Here's what I tried:

//@Min and @Max don't produce the intended behavior when combined with @Value
@Min(0)
@Max(100)
@Value("${tolerance.percentage:25}")
private int tolerance;

Can I enforce a min and max on an int field that @Value is aware of?

oberlies
  • 11,503
  • 4
  • 63
  • 110
JellyRaptor
  • 725
  • 1
  • 8
  • 20
  • `@Value` injects the value at start-up time (for singletons, not sure if you have that bean as `prototype`); I don't think you will have the bean validation triggered (automatically) at that time... – x80486 May 16 '17 at 17:13
  • 3
    You will only get validation in `@ConfigurationProperties` classes not in regular classes. If you want to validate those values add an `@PostConstruct` method to check the range. Or inject it into the constructor and do the validation in there. – M. Deinum May 16 '17 at 18:16
  • @M.Deinum Adding `@ConfigurationProperties` to my pojo worked. After adding it, the annotations started working together as I thought they would. If you formalize your comment into an answer, I will accept it. – JellyRaptor May 16 '17 at 18:55

1 Answers1

10

Validation using regular validation API annotations is only going to work in certain circumstances.

  1. You have an implementation ('hibernate-validator') on the classpath
  2. The class they are in are used to bind externalized configuration

So instead of using @Value with those you probably want to create a class that contains the expected properties and use binding with @ConfigurationProperties. (and you might want to use @Range instead).

@ConfigurationProperties(prefix="tolerance")
public ToleranceProperties {

    @Range(min=1, max=100)
    private int percentage = 25; 

    // Here be a getter/setter
}

This combined on a @Configuration class add @ EnableConfigurationProperties(ToleranceProperties.class) and you can use it anywhere you need properties. (See typesafe configuration properties in the reference guide.

Note: You could also declare it as a @Component.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • I'll just add that setter is mandatory for this to work, while getter is not – zbstof Jun 07 '17 at 13:42
  • Having this without a `getter` doesn't make real sense, as how would you get the value? (unless it is part of another combined property). – M. Deinum Jun 07 '17 at 13:44
  • 1
    You could possibly annotate `@Component` with `@ConfigurationProperties`, and then this component would use those properties internally. – zbstof Jun 07 '17 at 13:52
  • 2
    You could but that is not the general use case for `@ConfigurationProperties`. – M. Deinum Jun 07 '17 at 13:53