Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MethodArgumentNotValidException is thrown instead of HandlerMethodValidationException for @Valid @RequestBody #31775

Closed
unwx opened this issue Dec 6, 2023 · 2 comments
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: invalid An issue that we don't feel is valid

Comments

@unwx
Copy link

unwx commented Dec 6, 2023

Environment

Spring Boot: 3.2.0
Spring: 6.1
Java: 21


Expected Behavior:

If a controller method parameter is invalid, a HandlerMethodValidationException should be thrown.

Observed Behavior:

If you attach a @Valid annotation to a parameter, a MethodArgumentNotValidException is thrown instead of a HandlerMethodValidationException.


Reproduce the error

Spring-initializer

Custom ExceptionHandler

@RestControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                  HttpHeaders headers,
                                                                  HttpStatusCode status,
                                                                  WebRequest request) {

        throw new IllegalStateException("should not be thrown?", ex);
    }

    @Override
    protected ResponseEntity<Object> handleHandlerMethodValidationException(HandlerMethodValidationException ex,
                                                                            HttpHeaders headers,
                                                                            HttpStatusCode status,
                                                                            WebRequest request) {

//        ex.visitResults(); I want to visit RequestBody?
        return new ResponseEntity<>(
                Map.of("validation_error", ex.getMessage()),
                HttpStatus.BAD_REQUEST
        );
    }
}

REST Controller

@RestController
public class ValidController {
    @PostMapping(
            value = "/test",
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    public ResponseEntity<Object> test(@Valid @RequestBody Body body) {
        return ResponseEntity.ok(Map.of("valid", true));
    }

    public record Body(
            @NotNull
            @Positive
            @JsonProperty("number")
            Integer number
    ) {
    }
}

Make request

curl --location 'http://localhost:8080/test' \
--header 'Content-Type: application/json' \
--data '{
    "number": -1
}'

Debugging Insights

In org.springframework.web.method.support.InvocableHandlerMethod, method invokeForRequest(...):

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {

	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
		logger.trace("Arguments: " + Arrays.toString(args));
	}

	Class<?>[] groups = getValidationGroups();
	if (shouldValidateArguments() && this.methodValidator != null) {
		this.methodValidator.applyArgumentValidation(
				getBean(), getBridgedMethod(), getMethodParameters(), args, groups);
	}

	Object returnValue = doInvoke(args);

	if (shouldValidateReturnValue() && this.methodValidator != null) {
		this.methodValidator.applyReturnValueValidation(
				getBean(), getBridgedMethod(), getReturnType(), returnValue, groups);
	}

	return returnValue;
}

The first line,
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
throws a MethodArgumentNotValidException right before the handler validation should be started:

if (shouldValidateArguments() && this.methodValidator != null) {,
where methodValidator is org.springframework.web.method.annotation.HandlerMethodValidator


MethodArgumentNotValidException is thrown by org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor in method resolveArgument(...):

if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
	throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}

Additionally

I also tried with @Validated.
I assume that the validation of controller method parameters, which currently throws exception MethodArgumentNotValidException , is deprecated and should be replaced. When is the new validation method introduced in version 6.1 expected to replace it? I might be mistaken in my understanding.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Dec 6, 2023
@sbrannen sbrannen added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Dec 6, 2023
@rstoyanchev
Copy link
Contributor

rstoyanchev commented Dec 14, 2023

Method validation is applied as an additional layer, only if necessary. If all you have is a single @Valid @ModelAttribute or @RequestBody it is validated through Bean Validation as an individual object and results in a MethodArgumentNotValidException with a single BindingResult. This has been in place for a long time and continues to work the same way.

Built-in method validation (new in 6.1) applies when any @Constraint annotations are placed directly on controller method parameters. The only way to apply Bean Validation in that case is through method validation. This results in a HandlerMethodValidationException with separate ParameterValidationResult for each parameter, and among those there may be a ParameterErrors (essentially a BindingResult) for @RequestBody or @ModelAttribute.

In short this is expected behavior, and you'll need to handle both, but the error information that each exposes is aligned and should be comparable.

@rstoyanchev rstoyanchev added the status: waiting-for-feedback We need additional information before we can continue label Dec 14, 2023
@unwx
Copy link
Author

unwx commented Dec 17, 2023

Thank you for the explanation!

In extreme cases, if it is necessary to ensure that MethodArgumentNotValidException was thrown due to a violation of a field in the @RequestBody entity, ex.getParameter().getParameterAnnotation(RequestBody.class) can be used for this purpose.

I have no questions/comments left.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Dec 17, 2023
@rstoyanchev rstoyanchev closed this as not planned Won't fix, can't repro, duplicate, stale Dec 18, 2023
@rstoyanchev rstoyanchev added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Dec 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

4 participants