diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java
new file mode 100644
index 00000000000..b1ffa481bde
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.method.annotation;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Locale;
+
+import org.springframework.context.MessageSource;
+import org.springframework.http.HttpStatus;
+import org.springframework.validation.beanvalidation.MethodValidationResult;
+import org.springframework.validation.beanvalidation.ParameterValidationResult;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.web.util.BindErrorUtils;
+
+/**
+ * {@link ResponseStatusException} that is also {@link MethodValidationResult}.
+ * Raised by {@link HandlerMethodValidator} in case of method validation errors
+ * on a web controller method.
+ *
+ *
The {@link #getStatusCode()} is 400 for input validation errors, and 500
+ * for validation errors on a return value.
+ *
+ * @author Rossen Stoyanchev
+ * @since 6.1
+ */
+@SuppressWarnings("serial")
+public class HandlerMethodValidationException extends ResponseStatusException implements MethodValidationResult {
+
+ private final MethodValidationResult validationResult;
+
+
+ public HandlerMethodValidationException(MethodValidationResult validationResult) {
+ super(initHttpStatus(validationResult), "Validation failure", null, null, null);
+ this.validationResult = validationResult;
+ }
+
+ private static HttpStatus initHttpStatus(MethodValidationResult validationResult) {
+ return (!validationResult.isForReturnValue() ? HttpStatus.BAD_REQUEST : HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
+
+ @Override
+ public Object[] getDetailMessageArguments(MessageSource messageSource, Locale locale) {
+ return new Object[] { BindErrorUtils.resolveAndJoin(getAllErrors(), messageSource, locale) };
+ }
+
+ @Override
+ public Object[] getDetailMessageArguments() {
+ return new Object[] { BindErrorUtils.resolveAndJoin(getAllErrors()) };
+ }
+
+ @Override
+ public Object getTarget() {
+ return this.validationResult.getTarget();
+ }
+
+ @Override
+ public Method getMethod() {
+ return this.validationResult.getMethod();
+ }
+
+ @Override
+ public boolean isForReturnValue() {
+ return this.validationResult.isForReturnValue();
+ }
+
+ @Override
+ public List getAllValidationResults() {
+ return this.validationResult.getAllValidationResults();
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java
index 3e7a1265fae..36605ff4a54 100644
--- a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidator.java
@@ -27,7 +27,6 @@ import org.springframework.lang.Nullable;
import org.springframework.validation.BindingResult;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.beanvalidation.MethodValidationAdapter;
-import org.springframework.validation.beanvalidation.MethodValidationException;
import org.springframework.validation.beanvalidation.MethodValidationResult;
import org.springframework.validation.beanvalidation.MethodValidator;
import org.springframework.validation.beanvalidation.ParameterErrors;
@@ -91,7 +90,7 @@ public final class HandlerMethodValidator implements MethodValidator {
}
}
- throw new MethodValidationException(result);
+ throw new HandlerMethodValidationException(result);
}
@Override
@@ -109,7 +108,7 @@ public final class HandlerMethodValidator implements MethodValidator {
MethodValidationResult result = validateReturnValue(target, method, returnType, returnValue, groups);
if (result.hasErrors()) {
- throw new MethodValidationException(result);
+ throw new HandlerMethodValidationException(result);
}
}
diff --git a/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java b/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java
index 2570486832f..3c5ced38f87 100644
--- a/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java
+++ b/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java
@@ -20,13 +20,11 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
-import java.util.function.BiFunction;
import org.junit.jupiter.api.Test;
import org.springframework.beans.testfixture.beans.TestBean;
-import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
@@ -36,9 +34,9 @@ import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.validation.BindException;
+import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
-import org.springframework.validation.ObjectError;
+import org.springframework.validation.beanvalidation.MethodValidationResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingMatrixVariableException;
import org.springframework.web.bind.MissingPathVariableException;
@@ -48,6 +46,7 @@ import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
+import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.server.MethodNotAllowedException;
import org.springframework.web.server.MissingRequestValueException;
@@ -59,6 +58,9 @@ import org.springframework.web.testfixture.method.ResolvableMethod;
import org.springframework.web.util.BindErrorUtils;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.mock;
+import static org.mockito.BDDMockito.reset;
+import static org.mockito.BDDMockito.when;
/**
* Unit tests that verify the HTTP response details exposed by exceptions in the
@@ -245,20 +247,35 @@ public class ErrorResponseExceptionTests {
@Test
void methodArgumentNotValidException() {
- MessageSourceTestHelper messageSourceHelper = new MessageSourceTestHelper(MethodArgumentNotValidException.class);
- BindingResult bindingResult = messageSourceHelper.initBindingResult();
+ ValidationTestHelper testHelper = new ValidationTestHelper(MethodArgumentNotValidException.class);
+ BindingResult result = testHelper.bindingResult();
- MethodArgumentNotValidException ex = new MethodArgumentNotValidException(this.methodParameter, bindingResult);
+ MethodArgumentNotValidException ex = new MethodArgumentNotValidException(this.methodParameter, result);
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Invalid request content.");
- messageSourceHelper.assertDetailMessage(ex);
- messageSourceHelper.assertErrorMessages(
- (source, locale) -> BindErrorUtils.resolve(ex.getAllErrors(), source, locale));
+ testHelper.assertMessages(ex, ex.getAllErrors());
assertThat(ex.getHeaders()).isEmpty();
}
+ @Test
+ void handlerMethodValidationException() {
+ MethodValidationResult result = mock(MethodValidationResult.class);
+ when(result.isForReturnValue()).thenReturn(false);
+ HandlerMethodValidationException ex = new HandlerMethodValidationException(result);
+
+ assertStatus(ex, HttpStatus.BAD_REQUEST);
+ assertDetail(ex, "Validation failure");
+
+ reset(result);
+ when(result.isForReturnValue()).thenReturn(true);
+ ex = new HandlerMethodValidationException(result);
+
+ assertStatus(ex, HttpStatus.INTERNAL_SERVER_ERROR);
+ assertDetail(ex, "Validation failure");
+ }
+
@Test
void unsupportedMediaTypeStatusException() {
@@ -360,15 +377,14 @@ public class ErrorResponseExceptionTests {
@Test
void webExchangeBindException() {
- MessageSourceTestHelper messageSourceHelper = new MessageSourceTestHelper(WebExchangeBindException.class);
- BindingResult bindingResult = messageSourceHelper.initBindingResult();
+ ValidationTestHelper testHelper = new ValidationTestHelper(WebExchangeBindException.class);
+ BindingResult result = testHelper.bindingResult();
- WebExchangeBindException ex = new WebExchangeBindException(this.methodParameter, bindingResult);
+ WebExchangeBindException ex = new WebExchangeBindException(this.methodParameter, result);
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Invalid request content.");
- messageSourceHelper.assertDetailMessage(ex);
- messageSourceHelper.assertErrorMessages(ex::resolveErrorMessages);
+ testHelper.assertMessages(ex, ex.getAllErrors());
assertThat(ex.getHeaders()).isEmpty();
}
@@ -434,59 +450,52 @@ public class ErrorResponseExceptionTests {
private void handle(String arg) {}
- private static class MessageSourceTestHelper {
+ private static class ValidationTestHelper {
- private final String code;
+ private final BindingResult bindingResult;
- public MessageSourceTestHelper(Class extends ErrorResponse> exceptionType) {
- this.code = "problemDetail." + exceptionType.getName();
- }
+ private final StaticMessageSource messageSource = new StaticMessageSource();
+
+ public ValidationTestHelper(Class extends ErrorResponse> exceptionType) {
- public BindingResult initBindingResult() {
- BindingResult bindingResult = new BindException(new TestBean(), "myBean");
- bindingResult.reject("bean.invalid.A", "Invalid bean message");
- bindingResult.reject("bean.invalid.B");
- bindingResult.rejectValue("name", "name.required", "must be provided");
- bindingResult.rejectValue("age", "age.min");
- return bindingResult;
+ this.bindingResult = new BeanPropertyBindingResult(new TestBean(), "myBean");
+ this.bindingResult.reject("bean.invalid.A", "Invalid bean message");
+ this.bindingResult.reject("bean.invalid.B");
+ this.bindingResult.rejectValue("name", "name.required", "must be provided");
+ this.bindingResult.rejectValue("age", "age.min");
+
+ String code = "problemDetail." + exceptionType.getName();
+ this.messageSource.addMessage(code, Locale.UK, "Failed because {0}. Also because {1}");
+ this.messageSource.addMessage("bean.invalid.A", Locale.UK, "Bean A message");
+ this.messageSource.addMessage("bean.invalid.B", Locale.UK, "Bean B message");
+ this.messageSource.addMessage("name.required", Locale.UK, "name is required");
+ this.messageSource.addMessage("age.min", Locale.UK, "age is below minimum");
}
- private void assertDetailMessage(ErrorResponse ex) {
+ public BindingResult bindingResult() {
+ return this.bindingResult;
+ }
- StaticMessageSource messageSource = initMessageSource();
+ private void assertMessages(ErrorResponse ex, List extends MessageSourceResolvable> errors) {
- String message = messageSource.getMessage(
+ String message = this.messageSource.getMessage(
ex.getDetailMessageCode(), ex.getDetailMessageArguments(), Locale.UK);
assertThat(message).isEqualTo(
"Failed because Invalid bean message, and bean.invalid.B.myBean. " +
"Also because name: must be provided, and age: age.min.myBean.age");
- message = messageSource.getMessage(
- ex.getDetailMessageCode(), ex.getDetailMessageArguments(messageSource, Locale.UK), Locale.UK);
+ message = this.messageSource.getMessage(
+ ex.getDetailMessageCode(), ex.getDetailMessageArguments(this.messageSource, Locale.UK), Locale.UK);
assertThat(message).isEqualTo(
"Failed because Bean A message, and Bean B message. " +
"Also because name is required, and age is below minimum");
- }
-
- private void assertErrorMessages(BiFunction> expectedMessages) {
- StaticMessageSource messageSource = initMessageSource();
- Map map = expectedMessages.apply(messageSource, Locale.UK);
- assertThat(map).hasSize(4).containsValues(
- "Bean A message", "Bean B message", "name is required", "age is below minimum");
+ assertThat(BindErrorUtils.resolve(errors, this.messageSource, Locale.UK)).hasSize(4)
+ .containsValues("Bean A message", "Bean B message", "name is required", "age is below minimum");
}
- private StaticMessageSource initMessageSource() {
- StaticMessageSource messageSource = new StaticMessageSource();
- messageSource.addMessage(this.code, Locale.UK, "Failed because {0}. Also because {1}");
- messageSource.addMessage("bean.invalid.A", Locale.UK, "Bean A message");
- messageSource.addMessage("bean.invalid.B", Locale.UK, "Bean B message");
- messageSource.addMessage("name.required", Locale.UK, "name is required");
- messageSource.addMessage("age.min", Locale.UK, "age is below minimum");
- return messageSource;
- }
}
}
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandler.java
index 367bd8de351..c9bed00458c 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandler.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,15 +25,18 @@ import reactor.core.publisher.Mono;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
+import org.springframework.validation.beanvalidation.MethodValidationException;
import org.springframework.web.ErrorResponse;
import org.springframework.web.ErrorResponseException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.support.WebExchangeBindException;
+import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.server.MethodNotAllowedException;
import org.springframework.web.server.MissingRequestValueException;
import org.springframework.web.server.NotAcceptableStatusException;
@@ -97,10 +100,12 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
MissingRequestValueException.class,
UnsatisfiedRequestParameterException.class,
WebExchangeBindException.class,
+ HandlerMethodValidationException.class,
ServerWebInputException.class,
ServerErrorException.class,
ResponseStatusException.class,
- ErrorResponseException.class
+ ErrorResponseException.class,
+ MethodValidationException.class
})
public final Mono> handleException(Exception ex, ServerWebExchange exchange) {
if (ex instanceof MethodNotAllowedException theEx) {
@@ -121,6 +126,9 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
else if (ex instanceof WebExchangeBindException theEx) {
return handleWebExchangeBindException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
+ else if (ex instanceof HandlerMethodValidationException theEx) {
+ return handleHandlerMethodValidationException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
+ }
else if (ex instanceof ServerWebInputException theEx) {
return handleServerWebInputException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
@@ -133,6 +141,9 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
else if (ex instanceof ErrorResponseException theEx) {
return handleErrorResponseException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
}
+ else if (ex instanceof MethodValidationException theEx) {
+ return handleMethodValidationException(theEx, HttpStatus.INTERNAL_SERVER_ERROR, exchange);
+ }
else {
if (logger.isWarnEnabled()) {
logger.warn("Unexpected exception type: " + ex.getClass().getName());
@@ -237,6 +248,23 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
return handleExceptionInternal(ex, null, headers, status, exchange);
}
+ /**
+ * Customize the handling of {@link HandlerMethodValidationException}.
+ *
This method delegates to {@link #handleExceptionInternal}.
+ * @param ex the exception to handle
+ * @param headers the headers to use for the response
+ * @param status the status code to use for the response
+ * @param exchange the current request and response
+ * @return a {@code Mono} with the {@code ResponseEntity} for the response
+ * @since 6.1
+ */
+ protected Mono> handleHandlerMethodValidationException(
+ HandlerMethodValidationException ex, HttpHeaders headers, HttpStatusCode status,
+ ServerWebExchange exchange) {
+
+ return handleExceptionInternal(ex, null, headers, status, exchange);
+ }
+
/**
* Customize the handling of {@link ServerWebInputException}.
*
This method delegates to {@link #handleExceptionInternal}.
@@ -301,6 +329,22 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
return handleExceptionInternal(ex, null, headers, status, exchange);
}
+ /**
+ * Customize the handling of {@link MethodValidationException}.
+ *
This method delegates to {@link #handleExceptionInternal}.
+ * @param ex the exception to handle
+ * @param status the status code to use for the response
+ * @param exchange the current request and response
+ * @return a {@code Mono} with the {@code ResponseEntity} for the response
+ * @since 6.1
+ */
+ protected Mono> handleMethodValidationException(
+ MethodValidationException ex, HttpStatus status, ServerWebExchange exchange) {
+
+ ProblemDetail body = createProblemDetail(ex, status, "Validation failed", null, null, exchange);
+ return handleExceptionInternal(ex, body, null, status, exchange);
+ }
+
/**
* Convenience method to create a {@link ProblemDetail} for any exception
* that doesn't implement {@link ErrorResponse}, also performing a
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java
index a2649c6e527..ee4ad52099e 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MethodValidationTests.java
@@ -43,7 +43,6 @@ import org.springframework.validation.FieldError;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
-import org.springframework.validation.beanvalidation.MethodValidationException;
import org.springframework.validation.beanvalidation.ParameterValidationResult;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
import org.springframework.web.bind.WebDataBinder;
@@ -55,6 +54,7 @@ import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
@@ -202,7 +202,7 @@ public class MethodValidationTests {
StepVerifier.create(this.handlerAdapter.handle(exchange, hm))
.consumeErrorWith(throwable -> {
- MethodValidationException ex = (MethodValidationException) throwable;
+ HandlerMethodValidationException ex = (HandlerMethodValidationException) throwable;
assertThat(this.jakartaValidator.getValidationCount()).isEqualTo(1);
assertThat(this.jakartaValidator.getMethodValidationCount()).isEqualTo(1);
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandlerTests.java
index c098c2f088c..6da9bb5b117 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandlerTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandlerTests.java
@@ -37,9 +37,12 @@ import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.validation.BeanPropertyBindingResult;
+import org.springframework.validation.beanvalidation.MethodValidationException;
+import org.springframework.validation.beanvalidation.MethodValidationResult;
import org.springframework.web.ErrorResponse;
import org.springframework.web.ErrorResponseException;
import org.springframework.web.bind.support.WebExchangeBindException;
+import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.server.MethodNotAllowedException;
import org.springframework.web.server.MissingRequestValueException;
import org.springframework.web.server.NotAcceptableStatusException;
@@ -53,6 +56,7 @@ import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRe
import org.springframework.web.testfixture.server.MockServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.mock;
/**
* Unit tests for {@link ResponseEntityExceptionHandler}.
@@ -105,6 +109,21 @@ public class ResponseEntityExceptionHandlerTests {
testException(new WebExchangeBindException(null, new BeanPropertyBindingResult(new Object(), "foo")));
}
+ @Test
+ public void handlerMethodValidationException() {
+ testException(new HandlerMethodValidationException(mock(MethodValidationResult.class)));
+ }
+
+ @Test
+ public void methodValidationException() {
+ MethodValidationException ex = new MethodValidationException(mock(MethodValidationResult.class));
+ ResponseEntity> entity = this.exceptionHandler.handleException(ex, this.exchange).block();
+
+ assertThat(entity).isNotNull();
+ assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
+ assertThat(entity.getBody()).isInstanceOf(ProblemDetail.class);
+ }
+
@Test
void handleServerWebInputException() {
testException(new ServerWebInputException(""));
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java
index 489ee143449..fd696be71c0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java
@@ -34,6 +34,7 @@ import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.lang.Nullable;
import org.springframework.validation.BindException;
+import org.springframework.validation.beanvalidation.MethodValidationException;
import org.springframework.web.ErrorResponse;
import org.springframework.web.ErrorResponseException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
@@ -48,6 +49,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
+import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
@@ -121,6 +123,7 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
MissingServletRequestPartException.class,
ServletRequestBindingException.class,
MethodArgumentNotValidException.class,
+ HandlerMethodValidationException.class,
NoHandlerFoundException.class,
NoResourceFoundException.class,
AsyncRequestTimeoutException.class,
@@ -129,6 +132,7 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
+ MethodValidationException.class,
BindException.class
})
@Nullable
@@ -157,6 +161,9 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
else if (ex instanceof MethodArgumentNotValidException subEx) {
return handleMethodArgumentNotValid(subEx, subEx.getHeaders(), subEx.getStatusCode(), request);
}
+ else if (ex instanceof HandlerMethodValidationException subEx) {
+ return handleHandlerMethodValidationException(subEx, subEx.getHeaders(), subEx.getStatusCode(), request);
+ }
else if (ex instanceof NoHandlerFoundException subEx) {
return handleNoHandlerFoundException(subEx, subEx.getHeaders(), subEx.getStatusCode(), request);
}
@@ -185,6 +192,9 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
else if (ex instanceof HttpMessageNotWritableException theEx) {
return handleHttpMessageNotWritable(theEx, headers, HttpStatus.INTERNAL_SERVER_ERROR, request);
}
+ else if (ex instanceof MethodValidationException subEx) {
+ return handleMethodValidationException(subEx, headers, HttpStatus.INTERNAL_SERVER_ERROR, request);
+ }
else if (ex instanceof BindException theEx) {
return handleBindException(theEx, headers, HttpStatus.BAD_REQUEST, request);
}
@@ -335,6 +345,24 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
return handleExceptionInternal(ex, null, headers, status, request);
}
+ /**
+ * Customize the handling of {@link HandlerMethodValidationException}.
+ *
This method delegates to {@link #handleExceptionInternal}.
+ * @param ex the exception to handle
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return a {@code ResponseEntity} for the response to use, possibly
+ * {@code null} when the response is already committed
+ * @since 6.1
+ */
+ @Nullable
+ protected ResponseEntity