Bean validation is not working for spring webflux
Asked Answered
R

4

14

I have refactored my code to use spring webflux but now @Valid stopped working. It is not validating the request body.

@PostMapping(value = "/getContactInfo",produces = "application/json",consumes = "application/json")
public Flux<UserContactsModel> getUserContacts(@Valid @RequestBody Mono<LoginModel> loginDetail) {

    loginDetail.log();
    return contactInfoService
        .getUserContacts(loginDetailApiMapper.loginModelMonoToLoginBoMono(loginDetail))
        .flatMapIterable(
            userContactsBO -> contactInfoMapper.userContactBoToModelList(userContactsBO));
}

I am getting 200 OK in place of Bad request which I am returning from the controller advice.

Edit 1:

   import javax.validation.constraints.NotNull;
   import javax.validation.constraints.Pattern;

    public class LoginModel implements Serializable {

      private String clientId;

      @Pattern(regexp = "^[a-zA-Z0-9]*$", message = "Login ID is invalid")
      @NotNull
      private String loginId;
    }

update 1: After changing the code like this and adding @Validated on class level

@RestController
@Validated
public class ContactInfoController implements ContactInfoApi {
public Flux<UserContactsModel> getUserContacts(@RequestBody  Mono<@Valid  LoginModel> loginDetail) {

I am getting javax.validation.ConstraintDeclarationException: HV000197: No value extractor found for type parameter 'T' of type reactor.core.publisher.Mono.

Reredos answered 21/12, 2018 at 16:21 Comment(0)
R
1

Nothing worked for me. So I validated it manually by using javax.validator.

@Autowired private Validator validator;

 public Flux<UserContactsModel> getUserContacts(@RequestBody Mono<@Valid LoginModel> loginDetail) {

    return loginDetail
        .filter(this::validate)
        .map(....);
}

 private boolean validate(LoginModel loginModel) {

    Set<ConstraintViolation<LoginModel>> constraintViolations = validator.validate(loginModel);

    if (CollectionUtils.isNotEmpty(constraintViolations)) {
      StringJoiner stringJoiner = new StringJoiner(" ");
      constraintViolations.forEach(
          loginModelConstraintViolation ->
              stringJoiner
                  .add(loginModelConstraintViolation.getPropertyPath().toString())
                  .add(":")
                  .add(loginModelConstraintViolation.getMessage()));
      throw new RuntimeException(stringJoiner.toString());
    }

    return true;
  }
Reredos answered 28/12, 2018 at 15:37 Comment(1)
Not better? :) github.com/spring-projects/spring-boot/issues/…Kahle
D
5

For me the @Valid worked out of the box, the missing piece was adding spring-boot-starter-validation in the classpath:

  <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
  </dependency>

I have the exact same controller code as in this question plus a ExceptionHandler to handle the bean validation errors:

@ResponseBody
@ExceptionHandler(WebExchangeBindException.class)
Mono<ResponseEntity<List<ErrorDTO>>> invalidRequestErrorHandler(@NotNull final WebExchangeBindException e) {
    log.error("Invalid request exception occurred", e);
    var errors = e.getBindingResult()
            .getAllErrors()
            .stream()
            .filter(Objects::nonNull)
            .map(this::getValidationErrorMessage)
            .toList();
    return Mono.just(ResponseEntity.status(BAD_REQUEST)
            .contentType(APPLICATION_JSON)
            .body(errors));
}

@NotNull
private ErrorDTO getValidationErrorMessage(@NotNull final ObjectError error) {
    final var errorMessage = new StringBuilder();
    if (error instanceof FieldError fe) {
        errorMessage.append(fe.getField()).append(" - ");
    }
    errorMessage.append(error.getDefaultMessage());
    return new ErrorDTO()
            .errorCode(GENERIC_ERROR).message(errorMessage.toString());
}
Doane answered 9/1, 2022 at 9:6 Comment(1)
This also worked for me. The compilation works fine without this as annotations and classes are present, but the configuration is not configured without. Thanks!Flotow
R
1

Nothing worked for me. So I validated it manually by using javax.validator.

@Autowired private Validator validator;

 public Flux<UserContactsModel> getUserContacts(@RequestBody Mono<@Valid LoginModel> loginDetail) {

    return loginDetail
        .filter(this::validate)
        .map(....);
}

 private boolean validate(LoginModel loginModel) {

    Set<ConstraintViolation<LoginModel>> constraintViolations = validator.validate(loginModel);

    if (CollectionUtils.isNotEmpty(constraintViolations)) {
      StringJoiner stringJoiner = new StringJoiner(" ");
      constraintViolations.forEach(
          loginModelConstraintViolation ->
              stringJoiner
                  .add(loginModelConstraintViolation.getPropertyPath().toString())
                  .add(":")
                  .add(loginModelConstraintViolation.getMessage()));
      throw new RuntimeException(stringJoiner.toString());
    }

    return true;
  }
Reredos answered 28/12, 2018 at 15:37 Comment(1)
Not better? :) github.com/spring-projects/spring-boot/issues/…Kahle
P
0

Use @Validated in Service class, and @Valid in Entity/DTO parameter of method what you need validation. Libs: jakarta.validation-api and spring-boot-starter-validation

Persecution answered 6/9, 2023 at 21:32 Comment(0)
I
-2

@Valid annotation validates an object. So you are trying to validate a Mono, you need to change to LoginModel object, for example:

  ..getUserContacts(@RequestBody Mono<@Valid LoginModel> loginDetail) {
      ...
  }
Ia answered 21/12, 2018 at 17:13 Comment(8)
I tried this as well but didn't work. Do I need something else?Reredos
can you post LoginModel class? are you using some custom validator?Ia
Edited. Added login model.Reredos
javax.validation.ConstraintDeclarationException: HV000197: No value extractor found for type parameter 'T' of type reactor.core.publisher.Mono. . Getting this nowReredos
I am getting this after adding @Validated at the class level otherwise no validation happens and I got 200 OK response as beforeReredos
Adding @Validated over class header won't be able to help you. remove that. I'm going to try to simulate your case...Ia
OK then add just these fields and remove Validated annotation. RequestBody @Valid LoginModel loginDetailIa
them it will make it non reactiveReredos

© 2022 - 2024 — McMap. All rights reserved.