diff --git a/spring-web/src/main/java/org/springframework/web/bind/ServletRequestBindingException.java b/spring-web/src/main/java/org/springframework/web/bind/ServletRequestBindingException.java index f66ff06412a..cd42052b667 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/ServletRequestBindingException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/ServletRequestBindingException.java @@ -25,8 +25,8 @@ import org.springframework.lang.Nullable; import org.springframework.web.ErrorResponse; /** - * Fatal binding exception, thrown when we want to - * treat binding exceptions as unrecoverable. + * Fatal binding exception, thrown when we want to treat binding exceptions + * as unrecoverable. * *

Extends ServletException for convenient throwing in any Servlet resource * (such as a Filter), and NestedServletException for proper root cause handling @@ -38,13 +38,14 @@ import org.springframework.web.ErrorResponse; @SuppressWarnings("serial") public class ServletRequestBindingException extends ServletException implements ErrorResponse { - private final ProblemDetail body = ProblemDetail.forStatus(getStatusCode()); - private final String messageDetailCode; @Nullable private final Object[] messageDetailArguments; + @Nullable + private ProblemDetail body; + /** * Constructor with a message only. @@ -108,7 +109,10 @@ public class ServletRequestBindingException extends ServletException implements } @Override - public ProblemDetail getBody() { + public synchronized ProblemDetail getBody() { + if (this.body == null) { + this.body = ProblemDetail.forStatus(getStatusCode()); + } return this.body; } 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 177b8b4df1b..f55667bfafc 100644 --- a/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java +++ b/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java @@ -78,7 +78,6 @@ class ErrorResponseExceptionTests { @Test void httpMediaTypeNotSupportedException() { - List mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR); @@ -96,7 +95,6 @@ class ErrorResponseExceptionTests { @Test void httpMediaTypeNotSupportedExceptionWithParseError() { - ErrorResponse ex = new HttpMediaTypeNotSupportedException( "Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'"); @@ -109,7 +107,6 @@ class ErrorResponseExceptionTests { @Test void httpMediaTypeNotAcceptableException() { - List mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR); HttpMediaTypeNotAcceptableException ex = new HttpMediaTypeNotAcceptableException(mediaTypes); @@ -123,7 +120,6 @@ class ErrorResponseExceptionTests { @Test void httpMediaTypeNotAcceptableExceptionWithParseError() { - ErrorResponse ex = new HttpMediaTypeNotAcceptableException( "Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'"); @@ -136,7 +132,6 @@ class ErrorResponseExceptionTests { @Test void asyncRequestTimeoutException() { - ErrorResponse ex = new AsyncRequestTimeoutException(); assertDetailMessageCode(ex, null, null); @@ -148,7 +143,6 @@ class ErrorResponseExceptionTests { @Test void httpRequestMethodNotSupportedException() { - HttpRequestMethodNotSupportedException ex = new HttpRequestMethodNotSupportedException("PUT", Arrays.asList("GET", "POST")); @@ -162,7 +156,6 @@ class ErrorResponseExceptionTests { @Test void missingRequestHeaderException() { - MissingRequestHeaderException ex = new MissingRequestHeaderException("Authorization", this.methodParameter); assertStatus(ex, HttpStatus.BAD_REQUEST); @@ -174,7 +167,6 @@ class ErrorResponseExceptionTests { @Test void missingServletRequestParameterException() { - MissingServletRequestParameterException ex = new MissingServletRequestParameterException("query", "String"); assertStatus(ex, HttpStatus.BAD_REQUEST); @@ -186,10 +178,8 @@ class ErrorResponseExceptionTests { @Test void missingMatrixVariableException() { - MissingMatrixVariableException ex = new MissingMatrixVariableException("region", this.methodParameter); - assertStatus(ex, HttpStatus.BAD_REQUEST); assertDetail(ex, "Required path parameter 'region' is not present."); assertDetailMessageCode(ex, null, new Object[] {ex.getVariableName()}); @@ -199,7 +189,6 @@ class ErrorResponseExceptionTests { @Test void missingPathVariableException() { - MissingPathVariableException ex = new MissingPathVariableException("id", this.methodParameter); assertStatus(ex, HttpStatus.INTERNAL_SERVER_ERROR); @@ -210,8 +199,18 @@ class ErrorResponseExceptionTests { } @Test - void missingRequestCookieException() { + void missingPathVariableExceptionAfterConversion() { + MissingPathVariableException ex = new MissingPathVariableException("id", this.methodParameter, true); + + assertStatus(ex, HttpStatus.BAD_REQUEST); + assertDetail(ex, "Required path variable 'id' is not present."); + assertDetailMessageCode(ex, null, new Object[] {ex.getVariableName()}); + + assertThat(ex.getHeaders().isEmpty()).isTrue(); + } + @Test + void missingRequestCookieException() { MissingRequestCookieException ex = new MissingRequestCookieException("oreo", this.methodParameter); assertStatus(ex, HttpStatus.BAD_REQUEST); @@ -223,7 +222,6 @@ class ErrorResponseExceptionTests { @Test void unsatisfiedServletRequestParameterException() { - UnsatisfiedServletRequestParameterException ex = new UnsatisfiedServletRequestParameterException( new String[] { "foo=bar", "bar=baz" }, Collections.singletonMap("q", new String[] {"1"})); @@ -236,7 +234,6 @@ class ErrorResponseExceptionTests { @Test void missingServletRequestPartException() { - MissingServletRequestPartException ex = new MissingServletRequestPartException("file"); assertStatus(ex, HttpStatus.BAD_REQUEST); @@ -248,7 +245,6 @@ class ErrorResponseExceptionTests { @Test void methodArgumentNotValidException() { - ValidationTestHelper testHelper = new ValidationTestHelper(MethodArgumentNotValidException.class); BindingResult result = testHelper.bindingResult(); @@ -280,7 +276,6 @@ class ErrorResponseExceptionTests { @Test void unsupportedMediaTypeStatusException() { - List mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR); @@ -298,7 +293,6 @@ class ErrorResponseExceptionTests { @Test void unsupportedMediaTypeStatusExceptionWithParseError() { - ErrorResponse ex = new UnsupportedMediaTypeStatusException( "Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'"); @@ -311,7 +305,6 @@ class ErrorResponseExceptionTests { @Test void notAcceptableStatusException() { - List mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR); NotAcceptableStatusException ex = new NotAcceptableStatusException(mediaTypes); @@ -325,7 +318,6 @@ class ErrorResponseExceptionTests { @Test void notAcceptableStatusExceptionWithParseError() { - ErrorResponse ex = new NotAcceptableStatusException( "Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'"); @@ -338,7 +330,6 @@ class ErrorResponseExceptionTests { @Test void serverErrorException() { - ServerErrorException ex = new ServerErrorException("Failure", null); assertStatus(ex, HttpStatus.INTERNAL_SERVER_ERROR); @@ -350,7 +341,6 @@ class ErrorResponseExceptionTests { @Test void missingRequestValueException() { - MissingRequestValueException ex = new MissingRequestValueException("foo", String.class, "header", this.methodParameter); @@ -363,7 +353,6 @@ class ErrorResponseExceptionTests { @Test void unsatisfiedRequestParameterException() { - UnsatisfiedRequestParameterException ex = new UnsatisfiedRequestParameterException( Arrays.asList("foo=bar", "bar=baz"), @@ -378,7 +367,6 @@ class ErrorResponseExceptionTests { @Test void webExchangeBindException() { - ValidationTestHelper testHelper = new ValidationTestHelper(WebExchangeBindException.class); BindingResult result = testHelper.bindingResult(); @@ -393,7 +381,6 @@ class ErrorResponseExceptionTests { @Test void methodNotAllowedException() { - List supportedMethods = Arrays.asList(HttpMethod.GET, HttpMethod.POST); MethodNotAllowedException ex = new MethodNotAllowedException(HttpMethod.PUT, supportedMethods); @@ -407,7 +394,6 @@ class ErrorResponseExceptionTests { @Test void methodNotAllowedExceptionWithoutSupportedMethods() { - MethodNotAllowedException ex = new MethodNotAllowedException(HttpMethod.PUT, Collections.emptyList()); assertStatus(ex, HttpStatus.METHOD_NOT_ALLOWED); @@ -417,9 +403,8 @@ class ErrorResponseExceptionTests { assertThat(ex.getHeaders()).isEmpty(); } - @Test // gh-30300 + @Test // gh-30300 void responseStatusException() { - Locale locale = Locale.UK; LocaleContextHolder.setLocale(locale); @@ -519,7 +504,6 @@ class ErrorResponseExceptionTests { assertThat(BindErrorUtils.resolve(errors, this.messageSource, Locale.UK)).hasSize(4) .containsValues("Bean A message", "Bean B message", "name is required", "age is below minimum"); } - } } diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index c54f1501ffe..129dad4d9bb 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -128,6 +128,7 @@ +