diff --git a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeException.java b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeException.java index 0cd03ddb2042..a5c807213c95 100644 --- a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeException.java +++ b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2022 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. @@ -22,6 +22,7 @@ import jakarta.servlet.ServletException; import org.springframework.http.MediaType; +import org.springframework.http.ProblemDetail; /** * Abstract base for exceptions related to media types. Adds a list of supported {@link MediaType MediaTypes}. @@ -30,10 +31,12 @@ * @since 3.0 */ @SuppressWarnings("serial") -public abstract class HttpMediaTypeException extends ServletException { +public abstract class HttpMediaTypeException extends ServletException implements ErrorResponse { private final List supportedMediaTypes; + private final ProblemDetail body = ProblemDetail.forRawStatusCode(getRawStatusCode()); + /** * Create a new HttpMediaTypeException. @@ -61,4 +64,9 @@ public List getSupportedMediaTypes() { return this.supportedMediaTypes; } + @Override + public ProblemDetail getBody() { + return this.body; + } + } diff --git a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotAcceptableException.java b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotAcceptableException.java index 36df3ae33239..13da2c1130e6 100644 --- a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotAcceptableException.java +++ b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotAcceptableException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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. @@ -18,10 +18,14 @@ import java.util.List; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.util.CollectionUtils; /** - * Exception thrown when the request handler cannot generate a response that is acceptable by the client. + * Exception thrown when the request handler cannot generate a response that is + * acceptable by the client. * * @author Arjen Poutsma * @since 3.0 @@ -30,11 +34,12 @@ public class HttpMediaTypeNotAcceptableException extends HttpMediaTypeException { /** - * Create a new HttpMediaTypeNotAcceptableException. - * @param message the exception message + * Constructor for when the {@code Accept} header cannot be parsed. + * @param message the parse error message */ public HttpMediaTypeNotAcceptableException(String message) { super(message); + getBody().setDetail("Could not parse Accept header"); } /** @@ -42,7 +47,23 @@ public HttpMediaTypeNotAcceptableException(String message) { * @param supportedMediaTypes the list of supported media types */ public HttpMediaTypeNotAcceptableException(List supportedMediaTypes) { - super("Could not find acceptable representation", supportedMediaTypes); + super("No acceptable representation", supportedMediaTypes); + } + + + @Override + public int getRawStatusCode() { + return HttpStatus.NOT_ACCEPTABLE.value(); + } + + @Override + public HttpHeaders getHeaders() { + if (CollectionUtils.isEmpty(getSupportedMediaTypes())) { + return HttpHeaders.EMPTY; + } + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(this.getSupportedMediaTypes()); + return headers; } } diff --git a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java index 13585aa8c985..c1665fd6d5bf 100644 --- a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java +++ b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -18,14 +18,19 @@ import java.util.List; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.lang.Nullable; +import org.springframework.util.CollectionUtils; /** * Exception thrown when a client POSTs, PUTs, or PATCHes content of a type * not supported by request handler. * * @author Arjen Poutsma + * @author Rossen Stoyanchev * @since 3.0 */ @SuppressWarnings("serial") @@ -34,6 +39,9 @@ public class HttpMediaTypeNotSupportedException extends HttpMediaTypeException { @Nullable private final MediaType contentType; + @Nullable + private final HttpMethod httpMethod; + /** * Create a new HttpMediaTypeNotSupportedException. @@ -42,6 +50,8 @@ public class HttpMediaTypeNotSupportedException extends HttpMediaTypeException { public HttpMediaTypeNotSupportedException(String message) { super(message); this.contentType = null; + this.httpMethod = null; + getBody().setDetail("Could not parse Content-Type"); } /** @@ -50,21 +60,38 @@ public HttpMediaTypeNotSupportedException(String message) { * @param supportedMediaTypes the list of supported media types */ public HttpMediaTypeNotSupportedException(@Nullable MediaType contentType, List supportedMediaTypes) { - this(contentType, supportedMediaTypes, "Content type '" + - (contentType != null ? contentType : "") + "' not supported"); + this(contentType, supportedMediaTypes, null); } /** * Create a new HttpMediaTypeNotSupportedException. * @param contentType the unsupported content type * @param supportedMediaTypes the list of supported media types - * @param msg the detail message + * @param httpMethod the HTTP method of the request + * @since 6.0 */ public HttpMediaTypeNotSupportedException(@Nullable MediaType contentType, - List supportedMediaTypes, String msg) { + List supportedMediaTypes, @Nullable HttpMethod httpMethod) { + + this(contentType, supportedMediaTypes, httpMethod, + "Content-Type " + (contentType != null ? "'" + contentType + "' " : "") + "is not supported"); + } - super(msg, supportedMediaTypes); + /** + * Create a new HttpMediaTypeNotSupportedException. + * @param contentType the unsupported content type + * @param supportedMediaTypes the list of supported media types + * @param httpMethod the HTTP method of the request + * @param message the detail message + * @since 6.0 + */ + public HttpMediaTypeNotSupportedException(@Nullable MediaType contentType, + List supportedMediaTypes, @Nullable HttpMethod httpMethod, String message) { + + super(message, supportedMediaTypes); this.contentType = contentType; + this.httpMethod = httpMethod; + getBody().setDetail("Content-Type " + this.contentType + " is not supported"); } @@ -76,4 +103,22 @@ public MediaType getContentType() { return this.contentType; } + @Override + public int getRawStatusCode() { + return HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(); + } + + @Override + public HttpHeaders getHeaders() { + if (CollectionUtils.isEmpty(getSupportedMediaTypes())) { + return HttpHeaders.EMPTY; + } + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(getSupportedMediaTypes()); + if (HttpMethod.PATCH.equals(this.httpMethod)) { + headers.setAcceptPatch(getSupportedMediaTypes()); + } + return headers; + } + } diff --git a/spring-web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java b/spring-web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java index fec3b285266b..e17b0564c04e 100644 --- a/spring-web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java +++ b/spring-web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -22,8 +22,12 @@ import jakarta.servlet.ServletException; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -34,13 +38,15 @@ * @since 2.0 */ @SuppressWarnings("serial") -public class HttpRequestMethodNotSupportedException extends ServletException { +public class HttpRequestMethodNotSupportedException extends ServletException implements ErrorResponse { private final String method; @Nullable private final String[] supportedMethods; + private final ProblemDetail body; + /** * Create a new HttpRequestMethodNotSupportedException. @@ -74,7 +80,7 @@ public HttpRequestMethodNotSupportedException(String method, @Nullable Collectio * @param supportedMethods the actually supported HTTP methods (may be {@code null}) */ public HttpRequestMethodNotSupportedException(String method, @Nullable String[] supportedMethods) { - this(method, supportedMethods, "Request method '" + method + "' not supported"); + this(method, supportedMethods, "Request method '" + method + "' is not supported"); } /** @@ -87,6 +93,8 @@ public HttpRequestMethodNotSupportedException(String method, @Nullable String[] super(msg); this.method = method; this.supportedMethods = supportedMethods; + this.body = ProblemDetail.forRawStatusCode(getRawStatusCode()) + .withDetail("Method '" + method + "' is not supported"); } @@ -123,4 +131,24 @@ public Set getSupportedHttpMethods() { return supportedMethods; } + @Override + public int getRawStatusCode() { + return HttpStatus.METHOD_NOT_ALLOWED.value(); + } + + @Override + public HttpHeaders getHeaders() { + if (ObjectUtils.isEmpty(this.supportedMethods)) { + return HttpHeaders.EMPTY; + } + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.ALLOW, StringUtils.arrayToDelimitedString(this.supportedMethods, ", ")); + return headers; + } + + @Override + public ProblemDetail getBody() { + return this.body; + } + } diff --git a/spring-web/src/main/java/org/springframework/web/bind/MethodArgumentNotValidException.java b/spring-web/src/main/java/org/springframework/web/bind/MethodArgumentNotValidException.java index 3638ac1411b3..bc404d5c6fa4 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MethodArgumentNotValidException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MethodArgumentNotValidException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -17,9 +17,12 @@ package org.springframework.web.bind; import org.springframework.core.MethodParameter; +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; +import org.springframework.web.ErrorResponse; /** * Exception to be thrown when validation on an argument annotated with {@code @Valid} fails. @@ -30,10 +33,12 @@ * @since 3.1 */ @SuppressWarnings("serial") -public class MethodArgumentNotValidException extends BindException { +public class MethodArgumentNotValidException extends BindException implements ErrorResponse { private final MethodParameter parameter; + private final ProblemDetail body; + /** * Constructor for {@link MethodArgumentNotValidException}. @@ -43,9 +48,20 @@ public class MethodArgumentNotValidException extends BindException { public MethodArgumentNotValidException(MethodParameter parameter, BindingResult bindingResult) { super(bindingResult); this.parameter = parameter; + this.body = ProblemDetail.forRawStatusCode(getRawStatusCode()).withDetail(initMessage(parameter)); } + @Override + public int getRawStatusCode() { + return HttpStatus.BAD_REQUEST.value(); + } + + @Override + public ProblemDetail getBody() { + return this.body; + } + /** * Return the method parameter that failed validation. */ @@ -55,9 +71,13 @@ public final MethodParameter getParameter() { @Override public String getMessage() { + return initMessage(this.parameter); + } + + private String initMessage(MethodParameter parameter) { StringBuilder sb = new StringBuilder("Validation failed for argument [") - .append(this.parameter.getParameterIndex()).append("] in ") - .append(this.parameter.getExecutable().toGenericString()); + .append(parameter.getParameterIndex()).append("] in ") + .append(parameter.getExecutable().toGenericString()); BindingResult bindingResult = getBindingResult(); if (bindingResult.getErrorCount() > 1) { sb.append(" with ").append(bindingResult.getErrorCount()).append(" errors"); diff --git a/spring-web/src/main/java/org/springframework/web/bind/MissingMatrixVariableException.java b/spring-web/src/main/java/org/springframework/web/bind/MissingMatrixVariableException.java index 39d038aae4c4..6be5f22762f8 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MissingMatrixVariableException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MissingMatrixVariableException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -57,6 +57,7 @@ public MissingMatrixVariableException( super("", missingAfterConversion); this.variableName = variableName; this.parameter = parameter; + getBody().setDetail("Required path parameter '" + this.variableName + "' is not present"); } diff --git a/spring-web/src/main/java/org/springframework/web/bind/MissingPathVariableException.java b/spring-web/src/main/java/org/springframework/web/bind/MissingPathVariableException.java index 1fd5e3f4ab30..6c023b627e98 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MissingPathVariableException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MissingPathVariableException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -17,6 +17,7 @@ package org.springframework.web.bind; import org.springframework.core.MethodParameter; +import org.springframework.http.HttpStatus; /** * {@link ServletRequestBindingException} subclass that indicates that a path @@ -59,6 +60,7 @@ public MissingPathVariableException( super("", missingAfterConversion); this.variableName = variableName; this.parameter = parameter; + getBody().setDetail("Required URI variable '" + this.variableName + "' is not present"); } @@ -83,4 +85,10 @@ public final MethodParameter getParameter() { return this.parameter; } + + @Override + public int getRawStatusCode() { + return HttpStatus.INTERNAL_SERVER_ERROR.value(); + } + } diff --git a/spring-web/src/main/java/org/springframework/web/bind/MissingRequestCookieException.java b/spring-web/src/main/java/org/springframework/web/bind/MissingRequestCookieException.java index fd113c2864a5..24f27afee707 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MissingRequestCookieException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MissingRequestCookieException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -57,6 +57,7 @@ public MissingRequestCookieException( super("", missingAfterConversion); this.cookieName = cookieName; this.parameter = parameter; + getBody().setDetail("Required cookie '" + this.cookieName + "' is not present"); } diff --git a/spring-web/src/main/java/org/springframework/web/bind/MissingRequestHeaderException.java b/spring-web/src/main/java/org/springframework/web/bind/MissingRequestHeaderException.java index e60dfb169adb..00c3b4638262 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MissingRequestHeaderException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MissingRequestHeaderException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -57,6 +57,7 @@ public MissingRequestHeaderException( super("", missingAfterConversion); this.headerName = headerName; this.parameter = parameter; + getBody().setDetail("Required header '" + this.headerName + "' is not present"); } diff --git a/spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java b/spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java index 8bab4da052e2..2dfcf754b49f 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -52,6 +52,7 @@ public MissingServletRequestParameterException( super("", missingAfterConversion); this.parameterName = parameterName; this.parameterType = parameterType; + getBody().setDetail("Required parameter '" + this.parameterName + "' is not present"); } 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 fc7b7a6c0b46..a3bcc4a836f2 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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. @@ -16,6 +16,9 @@ package org.springframework.web.bind; +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; +import org.springframework.web.ErrorResponse; import org.springframework.web.util.NestedServletException; /** @@ -30,7 +33,10 @@ * @author Juergen Hoeller */ @SuppressWarnings("serial") -public class ServletRequestBindingException extends NestedServletException { +public class ServletRequestBindingException extends NestedServletException implements ErrorResponse { + + private final ProblemDetail body = ProblemDetail.forRawStatusCode(getRawStatusCode()); + /** * Constructor for ServletRequestBindingException. @@ -49,4 +55,15 @@ public ServletRequestBindingException(String msg, Throwable cause) { super(msg, cause); } + + @Override + public int getRawStatusCode() { + return HttpStatus.BAD_REQUEST.value(); + } + + @Override + public ProblemDetail getBody() { + return this.body; + } + } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncRequestTimeoutException.java b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncRequestTimeoutException.java index 084f8a516c2e..0a00eb4078ac 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncRequestTimeoutException.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncRequestTimeoutException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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. @@ -16,6 +16,10 @@ package org.springframework.web.context.request.async; +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; +import org.springframework.web.ErrorResponse; + /** * Exception to be thrown when an async request times out. * Alternatively an applications can register a @@ -30,6 +34,16 @@ * @since 4.2.8 */ @SuppressWarnings("serial") -public class AsyncRequestTimeoutException extends RuntimeException { +public class AsyncRequestTimeoutException extends RuntimeException implements ErrorResponse { + + @Override + public int getRawStatusCode() { + return HttpStatus.SERVICE_UNAVAILABLE.value(); + } + + @Override + public ProblemDetail getBody() { + return ProblemDetail.forRawStatusCode(getRawStatusCode()); + } } diff --git a/spring-web/src/main/java/org/springframework/web/multipart/support/MissingServletRequestPartException.java b/spring-web/src/main/java/org/springframework/web/multipart/support/MissingServletRequestPartException.java index 93f332fabf86..6719f8e67bd1 100644 --- a/spring-web/src/main/java/org/springframework/web/multipart/support/MissingServletRequestPartException.java +++ b/spring-web/src/main/java/org/springframework/web/multipart/support/MissingServletRequestPartException.java @@ -42,6 +42,7 @@ public class MissingServletRequestPartException extends ServletRequestBindingExc public MissingServletRequestPartException(String requestPartName) { super("Required request part '" + requestPartName + "' is not present"); this.requestPartName = requestPartName; + getBody().setDetail(getMessage()); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java index 29d291b72fd0..ee2f4603f8d3 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -156,7 +156,7 @@ public Mono handle(ServerWebExchange exchange) { private Mono createNotFoundError() { return Mono.defer(() -> { - Exception ex = new ResponseStatusException(HttpStatus.NOT_FOUND, "No matching handler"); + Exception ex = new ResponseStatusException(HttpStatus.NOT_FOUND); return Mono.error(ex); }); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java index b306e7299cb1..50ddba9e0e1b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -1261,8 +1261,7 @@ private void addAttributes(ServerWebExchange exchange, ServerRequest request) { } private Mono createNotFoundError() { - return Mono.defer(() -> Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, - "No matching router function"))); + return Mono.defer(() -> Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND))); } private static Mono wrapException(Supplier> supplier) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java index a581cc6e0c34..e347507b5949 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java @@ -200,8 +200,8 @@ protected HandlerMethod handleNoMatch(Set infos, if (helper.hasParamsMismatch()) { throw new ServerWebInputException( - "Unsatisfied query parameter conditions: " + helper.getParamConditions() + - ", actual parameters: " + request.getQueryParams()); + "Expected parameters: " + helper.getParamConditions() + + ", actual query parameters: " + request.getQueryParams()); } return null; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/DispatcherHandlerErrorTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/DispatcherHandlerErrorTests.java index f90fd6bc681b..7087cde65e80 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/DispatcherHandlerErrorTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/DispatcherHandlerErrorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -84,7 +84,7 @@ public void noHandler() { StepVerifier.create(mono) .consumeErrorWith(ex -> { assertThat(ex).isInstanceOf(ResponseStatusException.class); - assertThat(ex.getMessage()).isEqualTo("404 NOT_FOUND \"No matching handler\""); + assertThat(ex.getMessage()).isEqualTo("404 NOT_FOUND"); }) .verify(); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java index 64413cd40961..c5a421bb3a07 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2022 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. @@ -21,10 +21,13 @@ import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; +import org.springframework.web.ErrorResponse; /** - * By default when the DispatcherServlet can't find a handler for a request it - * sends a 404 response. However if its property "throwExceptionIfNoHandlerFound" + * By default, when the DispatcherServlet can't find a handler for a request it + * sends a 404 response. However, if its property "throwExceptionIfNoHandlerFound" * is set to {@code true} this exception is raised and may be handled with * a configured HandlerExceptionResolver. * @@ -34,7 +37,7 @@ * @see DispatcherServlet#noHandlerFound(HttpServletRequest, HttpServletResponse) */ @SuppressWarnings("serial") -public class NoHandlerFoundException extends ServletException { +public class NoHandlerFoundException extends ServletException implements ErrorResponse { private final String httpMethod; @@ -42,6 +45,8 @@ public class NoHandlerFoundException extends ServletException { private final HttpHeaders headers; + private final ProblemDetail detail = ProblemDetail.forRawStatusCode(getRawStatusCode()); + /** * Constructor for NoHandlerFoundException. @@ -57,6 +62,11 @@ public NoHandlerFoundException(String httpMethod, String requestURL, HttpHeaders } + @Override + public int getRawStatusCode() { + return HttpStatus.NOT_FOUND.value(); + } + public String getHttpMethod() { return this.httpMethod; } @@ -69,4 +79,9 @@ public HttpHeaders getHeaders() { return this.headers; } + @Override + public ProblemDetail getBody() { + return this.detail; + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java index 9ff4cd503baa..12c764c69081 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -206,7 +206,7 @@ private T bodyInternal(Type bodyType, Class bodyClass) throws ServletExce return theConverter.read(clazz, this.serverHttpRequest); } } - throw new HttpMediaTypeNotSupportedException(contentType, getSupportedMediaTypes(bodyClass)); + throw new HttpMediaTypeNotSupportedException(contentType, getSupportedMediaTypes(bodyClass), method()); } private List getSupportedMediaTypes(Class bodyClass) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequestBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequestBuilder.java index c3e6d93d83d8..e771d3cbc770 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequestBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequestBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -313,7 +313,7 @@ private T bodyInternal(Type bodyType, Class bodyClass) throws ServletExce return theConverter.read(clazz, inputMessage); } } - throw new HttpMediaTypeNotSupportedException(contentType, Collections.emptyList()); + throw new HttpMediaTypeNotSupportedException(contentType, Collections.emptyList(), method()); } @Override diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java index d321a36ead1e..f6a7eb42d3f1 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -264,7 +264,8 @@ protected HandlerMethod handleNoMatch( throw new HttpMediaTypeNotSupportedException(ex.getMessage()); } } - throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<>(mediaTypes)); + throw new HttpMediaTypeNotSupportedException( + contentType, new ArrayList<>(mediaTypes), HttpMethod.valueOf(request.getMethod())); } if (helper.hasProducesMismatch()) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java index 263f177abc99..6b726b36d82a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodArgumentResolver.java @@ -206,7 +206,7 @@ protected Object readWithMessageConverters(HttpInputMessage inputMessage, Me return null; } throw new HttpMediaTypeNotSupportedException(contentType, - getSupportedMediaTypes(targetClass != null ? targetClass : Object.class)); + getSupportedMediaTypes(targetClass != null ? targetClass : Object.class), httpMethod); } MediaType selectedContentType = contentType;