javaspring-webfluxhibernate-validator

Validating request body in Spring WebFlux


How do you validate a request DTO in a reactive Spring Web application?

In a regular Spring Web application, you would simply annotate the DTO with things like @Size, @Email and then put @Valid beside the controller method parameter, like so:

 import jakarta.validation.constraints.*;

    public class UserSignupRequestDTO {

        @NotBlank(message = "Email cannot be blank")
        @Email(message = "Email should be valid")
        private String email;

        @NotBlank(message = "Password cannot be blank")
        @Size(min = 8, message = "Password must be at least 8 characters long")
        private String password;
}
    @RestController
    public class SignupController {

        @PostMapping("/signup")
        public ResponseEntity<?> signup(@Valid @RequestBody UserSignupRequestDTO signupRequest) {
            // ...
        }
    }

However, it's not an option for a reactive handler:

@Configuration
public class RouterConfig {
    private final TokenHandler tokenHandler;

    public RouterConfig(TokenHandler tokenHandler) {
        this.tokenHandler = tokenHandler;
    }

    @Bean
    @RouterOperation(beanClass = TokenHandler.class, beanMethod = "signUp")
    public RouterFunction<ServerResponse> signUpRoute() {
        return RouterFunctions.route()
                .POST("/signup", tokenHandler::signUp)
                .build();
    }
}
// TokenHandler implementation
    @Override
    public Mono<ServerResponse> signUp(ServerRequest request) {
        return request.bodyToMono(UserSignupRequestDto.class) // this has to be validated
                .map(userMapper::toUser)
                .map(userService::save)
                .map(tokenService::generateTokenFor)
                .transform(jwt -> ServerResponse.status(HttpStatus.CREATED).body(jwt, String.class));
    }

What is Spring's suggested way of validating a request body DTO in such circumstances? I would still like to involve Jakarta's validation annotations without, obviously, writing any annotation-processing code myself.


Solution

  • As I've said in my comment, you can inject a Validator. The following successfully throws a ConstraintViolationException:

    // TokenHandler implementation
        @Autowired
        private Validator validator;
    
        @Override
        public Mono<ServerResponse> signUp(ServerRequest request) {
            return request.bodyToMono(UserSignupRequestDto.class) // this has to be validated
                    .map(this::validate)
                    .map(userMapper::toUser)
                    .map(userService::save)
                    .map(tokenService::generateTokenFor)
                    .transform(jwt -> ServerResponse.status(HttpStatus.CREATED).body(jwt, String.class));
        }
    
        private <T> T validate(T object) {
            Set<ConstraintViolation<T>> violations = validator.validate(object);
            if (violations.isEmpty()) {
                return object;
            }
            throw new ConstraintViolationException(violations);
        }
    

    Without any error handling in place that doesn't get turned into a nice error message, but that's a next step.