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
@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.