diff --git a/spring-web/src/main/java/org/springframework/web/ErrorResponse.java b/spring-web/src/main/java/org/springframework/web/ErrorResponse.java index 86e66a2adac..86c2d647210 100644 --- a/spring-web/src/main/java/org/springframework/web/ErrorResponse.java +++ b/spring-web/src/main/java/org/springframework/web/ErrorResponse.java @@ -95,8 +95,19 @@ public interface ErrorResponse { } /** - * Resolve the {@link #getDetailMessageCode() detailMessageCode} through the - * given {@link MessageSource}, and if found, update the "detail" field. + * Return a code to use to resolve the problem "detail" for this exception + * through a {@link MessageSource}. + *
By default this is initialized via
+ * {@link #getDefaultDetailMessageCode(Class, String)}.
+ */
+ default String getTitleCode() {
+ return getDefaultTitleMessageCode(getClass());
+ }
+
+ /**
+ * Resolve the {@link #getDetailMessageCode() detailMessageCode} and the
+ * {@link #getTitleCode() titleCode} through the given {@link MessageSource},
+ * and if found, update the "detail" and "title!" fields respectively.
* @param messageSource the {@code MessageSource} to use for the lookup
* @param locale the {@code Locale} to use for the lookup
*/
@@ -107,22 +118,35 @@ public interface ErrorResponse {
if (detail != null) {
getBody().setDetail(detail);
}
+ String title = messageSource.getMessage(getTitleCode(), null, null, locale);
+ if (title != null) {
+ getBody().setTitle(title);
+ }
}
return getBody();
}
/**
- * Build a message code for the given exception type, which consists of
- * {@code "problemDetail."} followed by the full {@link Class#getName() class name}.
- * @param exceptionType the exception type for which to build a code
+ * Build a message code for the "detail" field, for the given exception type.
+ * @param exceptionType the exception type associated with the problem
* @param suffix an optional suffix, e.g. for exceptions that may have multiple
* error message with different arguments.
+ * @return {@code "problemDetail."} followed by the full {@link Class#getName() class name}
*/
static String getDefaultDetailMessageCode(Class> exceptionType, @Nullable String suffix) {
return "problemDetail." + exceptionType.getName() + (suffix != null ? "." + suffix : "");
}
+ /**
+ * Build a message code for the "title" field, for the given exception type.
+ * @param exceptionType the exception type associated with the problem
+ * @return {@code "problemDetail.title."} followed by the full {@link Class#getName() class name}
+ */
+ static String getDefaultTitleMessageCode(Class> exceptionType) {
+ return "problemDetail.title." + exceptionType.getName();
+ }
+
/**
* Map the given Exception to an {@link ErrorResponse}.
* @param ex the Exception, mostly to derive message codes, if not provided
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 d448b45b383..74c7ecc300d 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
@@ -36,6 +36,7 @@ import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.validation.BeanPropertyBindingResult;
+import org.springframework.web.ErrorResponse;
import org.springframework.web.ErrorResponseException;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.server.MethodNotAllowedException;
@@ -131,8 +132,11 @@ public class ResponseEntityExceptionHandlerTests {
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage(
- "problemDetail." + UnsupportedMediaTypeStatusException.class.getName(), locale,
+ ErrorResponse.getDefaultDetailMessageCode(UnsupportedMediaTypeStatusException.class, null), locale,
"Content-Type {0} not supported. Supported: {1}");
+ messageSource.addMessage(
+ ErrorResponse.getDefaultTitleMessageCode(UnsupportedMediaTypeStatusException.class), locale,
+ "Media type is not valid or not supported");
this.exceptionHandler.setMessageSource(messageSource);
@@ -147,6 +151,8 @@ public class ResponseEntityExceptionHandlerTests {
ProblemDetail body = (ProblemDetail) responseEntity.getBody();
assertThat(body.getDetail()).isEqualTo(
"Content-Type application/json not supported. Supported: [application/atom+xml, application/xml]");
+ assertThat(body.getTitle()).isEqualTo(
+ "Media type is not valid or not supported");
}
@Test
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java
index dbdf0445919..ad8e1187eb7 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java
@@ -42,6 +42,7 @@ import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindException;
import org.springframework.validation.MapBindingResult;
+import org.springframework.web.ErrorResponse;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -166,8 +167,11 @@ public class ResponseEntityExceptionHandlerTests {
try {
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage(
- "problemDetail." + HttpMediaTypeNotSupportedException.class.getName(), locale,
+ ErrorResponse.getDefaultDetailMessageCode(HttpMediaTypeNotSupportedException.class, null), locale,
"Content-Type {0} not supported. Supported: {1}");
+ messageSource.addMessage(
+ ErrorResponse.getDefaultTitleMessageCode(HttpMediaTypeNotSupportedException.class), locale,
+ "Media type is not valid or not supported");
this.exceptionHandler.setMessageSource(messageSource);
@@ -177,6 +181,8 @@ public class ResponseEntityExceptionHandlerTests {
ProblemDetail body = (ProblemDetail) entity.getBody();
assertThat(body.getDetail()).isEqualTo(
"Content-Type application/json not supported. Supported: [application/atom+xml, application/xml]");
+ assertThat(body.getTitle()).isEqualTo(
+ "Media type is not valid or not supported");
}
finally {
LocaleContextHolder.resetLocaleContext();
@@ -201,7 +207,7 @@ public class ResponseEntityExceptionHandlerTests {
try {
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage(
- "problemDetail." + TypeMismatchException.class.getName(), locale,
+ ErrorResponse.getDefaultDetailMessageCode(TypeMismatchException.class, null), locale,
"Failed to set {0} to value: {1}");
this.exceptionHandler.setMessageSource(messageSource);
diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc
index 1ead716f3dc..62e5e9da8ad 100644
--- a/src/docs/asciidoc/web/webflux.adoc
+++ b/src/docs/asciidoc/web/webflux.adoc
@@ -3614,7 +3614,7 @@ and any `ErrorResponseException`, and renders an error response with a body.
[.small]#<