Environment

Spring Boot: 3.2.0-M3 Spring Web: spring-web-6.1.0-M5.jar Java: 17


Expected Behavior:

When a @RequestParam value is invalid, HandlerMethodValidationException.Visitor should call the requestParam(RequestParam, ParameterValidationResult) method.

Observed Behavior:

Instead, HandlerMethodValidationException.Visitor calls other(ParameterValidationResult).

Reproduce the error

Spring Initializr

@RestController
@RequestMapping("/api")
public class Controller {
    @GetMapping(
            value = "/invalid",
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Object> invalid(@RequestParam @Positive int num) {
        return ResponseEntity.ok(Map.of("positive", num));
    }
}
@RestControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler {
    @Override
    protected ResponseEntity<Object> handleHandlerMethodValidationException(HandlerMethodValidationException ex,
                                                                            HttpHeaders headers,
                                                                            HttpStatusCode status,
                                                                            WebRequest request) {

        AtomicReference<ResponseEntity<Object>> responseEntityRef = new AtomicReference<>();
        ex.visitResults(new HandlerMethodValidationException.Visitor() {
            /*
             * @Overrides...
             */

            @Override
            public void requestParam(RequestParam requestParam, ParameterValidationResult result) {
                responseEntityRef.set(new ResponseEntity<>(Map.of("invalid_param", ex.getMessage()), HttpStatus.BAD_REQUEST));
            }

            @Override
            public void other(ParameterValidationResult result) {
                throw new IllegalStateException("Why not @RequestParam?");
            }
        });

        return responseEntityRef.get();
    }
}

Execute

curl --location 'localhost:8080/api/invalid?num=-5'

Debugging Insights

In HandlerMethodValidationException#visitResults(HandlerMethodValidationException.Visitor):

if (this.requestParamPredicate.test(param)) {
    RequestParam requestParam = param.getParameterAnnotation(RequestParam.class);
    visitor.requestParam(requestParam, result);
    continue;
}

Line this.requestParamPredicate.test(param) is false, because: ↓ In org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#methodParamPredicate(List, Class)}

return parameter -> {
    for (HandlerMethodArgumentResolver resolver : resolvers) {
        if (resolver.supportsParameter(parameter)) {
            return resolverType.isInstance(resolver);
        }
    }
    return false;
};

Line return resolverType.isInstance(resolver); returns false, because: ↓ resolverType is class org.springframework.web.service.invoker.RequestParamArgumentResolver and resolver.getClass() is class org.springframework.web.method.annotation.RequestParamMethodArgumentResolver

The mismatch between resolverType and resolver.getClass() appears to be the crux of the problem.


Thank you for looking into this!

Comment From: rstoyanchev

Thanks for giving this a try and narrowing the cause.