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

371 views Asked by At

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?

3

There are 3 answers

1
Andrei Lisa On BEST ANSWER

If you want to do it with ConstraintValidator. You should to 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 a 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

2
Ouertani Aymen On

you can use an if statement to check if the start property is smaller than the end property and it it's true you do whatever treatment you want else you just return an error message but in this scenario you will have to change the return type to ResponseEntity

0
Mafei On

You can provide an extra method for @AssertTrue like below. that method will be called automatically and checks your condition.

@Getter
@Setter
public class EventRequestBody {
    private Long startTime;
    private Long endTime;

    // Getters and setters

    @AssertTrue(message = "startTime must be before endTime")
    private boolean isValidTimeRange() {
        return endTime.compareTo(startTime) > 0;
    }
}