Search code examples
spring-bootcustomvalidator

Simplest way to validate depending properties of @RequestBody with a custom validator


I got an endpoint looking like this:

@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
Event createEvent (@RequestBody(required = true) @Valid EventRequestBody eventRequestBody) {
.....
}

My EventRequestBody looks like this:

class EventRequestBody {
    @NotNull
    Long start //represents timestamp
    @NotNull
    Long end //represents timestamp
}

I already told spring boot that both properties are mandatory. But i also want spring boot to check that the start property has to be smaller than the end property. There are some threads out there to do this with custom validation. But all of them are confusing me and i really dont know which way is the most simple and properly working way.

Any suggestions?


Solution

  • If you want to do it with ConstraintValidator, you should do something like this: Declare a custom annotation:

    @Documented
    @Constraint(validatedBy = ValidatorValuesImpl.class)
    @Target({TYPE})
    @Retention(RUNTIME)
    public @interface ValidatorValues {
    
      String message() default "End before start";
    
      Class[] groups() default {};
    
      Class[] payload() default {};
    }
    

    Also an implementation of this annotation

    public class ValidatorValuesImpl implements ConstraintValidator<ValidatorValues, EventRequestBody> {
      @Override
      public void initialize(ValidatorValues constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
      }
    
      @Override
      public boolean isValid(EventRequestBody value, ConstraintValidatorContext context) {
        return value.getEnd().compareTo(value.getStart()) > 0;
      }
    }
    

    On your EventRequestBody class, add next one annotation @ValidatorValues

    The final result will look:

    @ValidatorValues
    class EventRequestBody {
        @NotNull
        Long start //represents timestamp
        @NotNull
        Long end //represents timestamp
    }
    

    And on controller/service layer, it depends on the situation. you will invoke next one method:

    private final Validator validator;
      ...
    @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    Event createEvent (@RequestBody(required = true) @Valid EventRequestBody eventRequestBody) {
      Set<ConstraintViolation<EventRequestBody>> validate = validator.validate(eventRequestBody);
        ...
    
    }
    

    For more details, check Validation.