0

This is quite a niche question but hopefully someone knows the answer:

I have a SpringFramework RestController with a GetMapping, that has some parameter validation using javax.validation annotations. I would like to use a property that's defined in application.yml, as the max value for a url parameter, but I get the compiler error that the variable should be a constant. See code:

@RestController
@Validated
public class AssetController {
  
    private final int maxWaitMillis;

    @Autowired
    public AssetController(@Value("${maxWaitMillis}") int maxWaitMillis) {
        this.maxWaitMillis = maxWaitMillis;
    }

    @GetMapping(value = "/asset", produces = MediaType.IMAGE_JPEG_VALUE)
    @ResponseBody
    public ResponseEntity<byte[]> getAsset(@RequestParam
                                           @NotBlank(message = "id should not be empty") String id,
                                           @RequestParam(defaultValue = "100", name = "timeout-ms")
                                           @Min(value = 0, message = "minimum timeout is 0 ms")
                                           @Max(value = maxWaitMillis, message = "max timeout is " + 

maxWaitMillis) int timeoutMs) {
... 
}

application.yml:

maxWaitMillis: 5000

At @Max(value = maxWaitMillis it says maxWaitMillis should be constant, because I guess the check within the annotation is done before the constructor can get the value from application.yml.

If I check the value of maxWaitMillis within the constructor or method body it does have a value of 5000.

Does anyone know if there's a way to use the value from application.yml in the validation annotations?

Thanks!

Lao
  • 55
  • 1
  • 7

2 Answers2

1

Create a custom validator as follows

@Target({ElementType.PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = MaxWaitMillisValidator.class)
@Documented
public @interface MaxWaitMillis {
    String message() default "Invalid max wait";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class MaxWaitMillisValidator implements ConstraintValidator<MaxWaitMillis, Integer> {
    @Value("${maxWaitMillis}")
    private int maxWaitMillis;

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return value <= maxWaitMillis;
    }
}

You can then use @MaxWaitMillis in place of @Max in the controller.

jtsnr
  • 1,180
  • 11
  • 18
0

The value for an annotation must be a compile time constant, so there is no simple way of doing what you are trying to do.

This is a constant variable, a type of constant expression:

private final int maxWaitMillis = 5000;

This is not:

private final int maxWaitMillis;

@Autowired
public AssetController(@Value("${maxWaitMillis}") int maxWaitMillis) {
    this.maxWaitMillis = maxWaitMillis;
}
İsmail Y.
  • 3,579
  • 5
  • 21
  • 29
  • That expression doesn't work in that way. It just sees a String where it expects an int – Lao Jan 08 '21 at 15:04
  • I think it will work for string values, the `int` part is wrong. Ex: `@GetMapping`value. – İsmail Y. Jan 08 '21 at 15:09
  • Could you show what you mean? I think it's not possible to use the "${} notation" for a compile time constant, so I'm looking for another way. I don't think it matters if the variable in application.yml is int or String – Lao Jan 08 '21 at 15:24
  • I would argue that it is, but `@GetMapping (value = "${property-name}")` works. – İsmail Y. Jan 08 '21 at 15:36
  • Hmm, I guess it doesn't work voor javax.validation then. @Max(value = "${maxWaitMillis}") gives the error `incompatible types: java.lang.String cannot be converted to long` – Lao Jan 08 '21 at 20:33