diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerSupport.java
new file mode 100644
index 00000000000..f9345ee72b7
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerSupport.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright 2002-2012 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
+ *
+ * http://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.servlet.mvc.method.annotation;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.ConversionNotSupportedException;
+import org.springframework.beans.TypeMismatchException;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.http.converter.HttpMessageNotWritableException;
+import org.springframework.util.CollectionUtils;
+import org.springframework.validation.BindException;
+import org.springframework.web.HttpMediaTypeNotAcceptableException;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.multipart.support.MissingServletRequestPartException;
+import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
+
+/**
+ * A convenient base for classes with {@link ExceptionHandler} methods providing
+ * infrastructure to handle standard Spring MVC exceptions. The functionality is
+ * equivalent to that of the {@link DefaultHandlerExceptionResolver} except it
+ * can be customized to write error content to the body of the response. If there
+ * is no need to write error content, use {@code DefaultHandlerExceptionResolver}
+ * instead.
+ *
+ *
It is expected the sub-classes will be annotated with
+ * {@link ControllerAdvice @ControllerAdvice} and that
+ * {@link ExceptionHandlerExceptionResolver} is configured to ensure this class
+ * applies to exceptions from any {@link RequestMapping @RequestMapping} method.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ *
+ * @see org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
+ */
+public abstract class ExceptionHandlerSupport {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /**
+ * Log category to use when no mapped handler is found for a request.
+ * @see #pageNotFoundLogger
+ */
+ public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
+
+ /**
+ * Additional logger to use when no mapped handler is found for a request.
+ * @see #PAGE_NOT_FOUND_LOG_CATEGORY
+ */
+ protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
+
+
+ /**
+ * Provides handling for standard Spring MVC exceptions.
+ * @param ex the target exception
+ * @param request the current request
+ */
+ @ExceptionHandler(value={
+ NoSuchRequestHandlingMethodException.class,
+ HttpRequestMethodNotSupportedException.class,
+ HttpMediaTypeNotSupportedException.class,
+ HttpMediaTypeNotAcceptableException.class,
+ MissingServletRequestParameterException.class,
+ ServletRequestBindingException.class,
+ ConversionNotSupportedException.class,
+ TypeMismatchException.class,
+ HttpMessageNotReadableException.class,
+ HttpMessageNotWritableException.class,
+ MethodArgumentNotValidException.class,
+ MissingServletRequestPartException.class,
+ BindException.class
+ })
+ public final ResponseEntity handleException(Exception ex, WebRequest request) {
+
+ HttpHeaders headers = new HttpHeaders();
+
+ HttpStatus status;
+ Object body;
+
+ if (ex instanceof NoSuchRequestHandlingMethodException) {
+ status = HttpStatus.NOT_FOUND;
+ body = handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, headers, status, request);
+ }
+ else if (ex instanceof HttpRequestMethodNotSupportedException) {
+ status = HttpStatus.METHOD_NOT_ALLOWED;
+ body = handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
+ }
+ else if (ex instanceof HttpMediaTypeNotSupportedException) {
+ status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
+ body = handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request);
+ }
+ else if (ex instanceof HttpMediaTypeNotAcceptableException) {
+ status = HttpStatus.NOT_ACCEPTABLE;
+ body = handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request);
+ }
+ else if (ex instanceof MissingServletRequestParameterException) {
+ status = HttpStatus.BAD_REQUEST;
+ body = handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request);
+ }
+ else if (ex instanceof ServletRequestBindingException) {
+ status = HttpStatus.BAD_REQUEST;
+ body = handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request);
+ }
+ else if (ex instanceof ConversionNotSupportedException) {
+ status = HttpStatus.INTERNAL_SERVER_ERROR;
+ body = handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request);
+ }
+ else if (ex instanceof TypeMismatchException) {
+ status = HttpStatus.BAD_REQUEST;
+ body = handleTypeMismatch((TypeMismatchException) ex, headers, status, request);
+ }
+ else if (ex instanceof HttpMessageNotReadableException) {
+ status = HttpStatus.BAD_REQUEST;
+ body = handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request);
+ }
+ else if (ex instanceof HttpMessageNotWritableException) {
+ status = HttpStatus.INTERNAL_SERVER_ERROR;
+ body = handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request);
+ }
+ else if (ex instanceof MethodArgumentNotValidException) {
+ status = HttpStatus.BAD_REQUEST;
+ body = handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request);
+ }
+ else if (ex instanceof MissingServletRequestPartException) {
+ status = HttpStatus.BAD_REQUEST;
+ body = handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request);
+ }
+ else if (ex instanceof BindException) {
+ status = HttpStatus.BAD_REQUEST;
+ body = handleBindException((BindException) ex, headers, status, request);
+ }
+ else {
+ logger.warn("Unknown exception type: " + ex.getClass().getName());
+ status = HttpStatus.INTERNAL_SERVER_ERROR;
+ body = handleExceptionInternal(ex, headers, status, request);
+ }
+
+ return new ResponseEntity(body, headers, status);
+ }
+
+ /**
+ * A single place to customize the response body of all Exception types.
+ * This method returns {@code null} by default.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ */
+ protected Object handleExceptionInternal(Exception ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
+ return null;
+ }
+
+ /**
+ * Customize the response for NoSuchRequestHandlingMethodException.
+ * This method logs a warning and delegates to
+ * {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+
+ pageNotFoundLogger.warn(ex.getMessage());
+
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+ /**
+ * Customize the response for HttpRequestMethodNotSupportedException.
+ * This method logs a warning, sets the "Allow" header, and delegates to
+ * {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+
+ pageNotFoundLogger.warn(ex.getMessage());
+
+ Set mediaTypes = new HashSet();
+ for (String value : ex.getSupportedMethods()) {
+ mediaTypes.add(HttpMethod.valueOf(value));
+ }
+ if (!mediaTypes.isEmpty()) {
+ headers.setAllow(mediaTypes);
+ }
+
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+ /**
+ * Customize the response for HttpMediaTypeNotSupportedException.
+ * This method sets the "Accept" header and delegates to
+ * {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+
+ List mediaTypes = ex.getSupportedMediaTypes();
+ if (!CollectionUtils.isEmpty(mediaTypes)) {
+ headers.setAccept(mediaTypes);
+ }
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+ /**
+ * Customize the response for HttpMediaTypeNotAcceptableException.
+ * This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+ /**
+ * Customize the response for MissingServletRequestParameterException.
+ * This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+ /**
+ * Customize the response for ServletRequestBindingException.
+ * This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleServletRequestBindingException(ServletRequestBindingException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+ /**
+ * Customize the response for ConversionNotSupportedException.
+ * This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleConversionNotSupported(ConversionNotSupportedException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+ /**
+ * Customize the response for TypeMismatchException.
+ * This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers,
+ HttpStatus status, WebRequest request) {
+
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+ /**
+ * Customize the response for HttpMessageNotReadableException.
+ * This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+ /**
+ * Customize the response for HttpMessageNotWritableException.
+ * This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleHttpMessageNotWritable(HttpMessageNotWritableException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+ /**
+ * Customize the response for MethodArgumentNotValidException.
+ * This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+ /**
+ * Customize the response for MissingServletRequestPartException.
+ * This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleMissingServletRequestPart(MissingServletRequestPartException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+ /**
+ * Customize the response for BindException.
+ * This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
+ * @param ex the exception
+ * @param headers the headers to be written to the response
+ * @param status the selected response status
+ * @param request the current request
+ * @return an Object or {@code null}
+ */
+ protected Object handleBindException(BindException ex, HttpHeaders headers,
+ HttpStatus status, WebRequest request) {
+
+ return handleExceptionInternal(ex, headers, status, request);
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java
index b597f9e49d0..104d9da3515 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java
@@ -59,6 +59,8 @@ import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMeth
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.0
+ *
+ * @see org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerSupport
* @see #handleNoSuchRequestHandlingMethod
* @see #handleHttpRequestMethodNotSupported
* @see #handleHttpMediaTypeNotSupported
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerSupportTests.java
new file mode 100644
index 00000000000..04d28018cf4
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerSupportTests.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2002-2012 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
+ *
+ * http://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.servlet.mvc.method.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.ConversionNotSupportedException;
+import org.springframework.beans.TypeMismatchException;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.http.converter.HttpMessageNotWritableException;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.validation.BindException;
+import org.springframework.web.HttpMediaTypeNotAcceptableException;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+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.support.StaticWebApplicationContext;
+import org.springframework.web.multipart.support.MissingServletRequestPartException;
+import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
+import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
+
+/**
+ * Test fixture for {@link ExceptionHandlerSupport}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class ExceptionHandlerSupportTests {
+
+ private ExceptionHandlerSupport exceptionHandlerSupport;
+
+ private DefaultHandlerExceptionResolver defaultExceptionResolver;
+
+ private WebRequest request;
+
+ private HttpServletRequest servletRequest;
+
+ private MockHttpServletResponse servletResponse;
+
+
+ @Before
+ public void setup() {
+ this.servletRequest = new MockHttpServletRequest();
+ this.servletResponse = new MockHttpServletResponse();
+ this.request = new ServletWebRequest(this.servletRequest, this.servletResponse);
+
+ this.exceptionHandlerSupport = new ApplicationExceptionHandler();
+ this.defaultExceptionResolver = new DefaultHandlerExceptionResolver();
+ }
+
+ @Test
+ public void supportsAllDefaultHandlerExceptionResolverExceptionTypes() throws Exception {
+
+ Method annotMethod = ExceptionHandlerSupport.class.getMethod("handleException", Exception.class, WebRequest.class);
+ ExceptionHandler annot = annotMethod.getAnnotation(ExceptionHandler.class);
+ List> supportedTypes = Arrays.asList(annot.value());
+
+ for (Method method : DefaultHandlerExceptionResolver.class.getDeclaredMethods()) {
+ Class>[] paramTypes = method.getParameterTypes();
+ if (method.getName().startsWith("handle") && (paramTypes.length == 4)) {
+ String name = paramTypes[0].getSimpleName();
+ assertTrue("@ExceptionHandler is missing " + name, supportedTypes.contains(paramTypes[0]));
+ }
+ }
+ }
+
+ @Test
+ public void noSuchRequestHandlingMethod() {
+ Exception ex = new NoSuchRequestHandlingMethodException("GET", TestController.class);
+ testException(ex);
+ }
+
+ @Test
+ public void httpRequestMethodNotSupported() {
+ List supported = Arrays.asList("POST", "DELETE");
+ Exception ex = new HttpRequestMethodNotSupportedException("GET", supported);
+
+ ResponseEntity responseEntity = testException(ex);
+ assertEquals(EnumSet.of(HttpMethod.POST, HttpMethod.DELETE), responseEntity.getHeaders().getAllow());
+
+ }
+
+ @Test
+ public void handleHttpMediaTypeNotSupported() {
+ List acceptable = Arrays.asList(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML);
+ Exception ex = new HttpMediaTypeNotSupportedException(MediaType.APPLICATION_JSON, acceptable);
+
+ ResponseEntity responseEntity = testException(ex);
+ assertEquals(acceptable, responseEntity.getHeaders().getAccept());
+ }
+
+ @Test
+ public void httpMediaTypeNotAcceptable() {
+ Exception ex = new HttpMediaTypeNotAcceptableException("");
+ testException(ex);
+ }
+
+ @Test
+ public void missingServletRequestParameter() {
+ Exception ex = new MissingServletRequestParameterException("param", "type");
+ testException(ex);
+ }
+
+ @Test
+ public void servletRequestBindingException() {
+ Exception ex = new ServletRequestBindingException("message");
+ testException(ex);
+ }
+
+ @Test
+ public void conversionNotSupported() {
+ Exception ex = new ConversionNotSupportedException(new Object(), Object.class, null);
+ testException(ex);
+ }
+
+ @Test
+ public void typeMismatch() {
+ Exception ex = new TypeMismatchException("foo", String.class);
+ testException(ex);
+ }
+
+ @Test
+ public void httpMessageNotReadable() {
+ Exception ex = new HttpMessageNotReadableException("message");
+ testException(ex);
+ }
+
+ @Test
+ public void httpMessageNotWritable() {
+ Exception ex = new HttpMessageNotWritableException("");
+ testException(ex);
+ }
+
+ @Test
+ public void methodArgumentNotValid() {
+ Exception ex = new MethodArgumentNotValidException(null, null);
+ testException(ex);
+ }
+
+ @Test
+ public void missingServletRequestPart() {
+ Exception ex = new MissingServletRequestPartException("partName");
+ testException(ex);
+ }
+
+ @Test
+ public void bindException() {
+ Exception ex = new BindException(new Object(), "name");
+ testException(ex);
+ }
+
+ @Test
+ public void controllerAdvice() throws Exception {
+ StaticWebApplicationContext cxt = new StaticWebApplicationContext();
+ cxt.registerSingleton("exceptionHandler", ApplicationExceptionHandler.class);
+ cxt.refresh();
+
+ ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
+ resolver.setApplicationContext(cxt);
+ resolver.afterPropertiesSet();
+
+ ServletRequestBindingException ex = new ServletRequestBindingException("message");
+ resolver.resolveException(this.servletRequest, this.servletResponse, null, ex);
+
+ assertEquals(400, this.servletResponse.getStatus());
+ assertEquals("error content", this.servletResponse.getContentAsString());
+ assertEquals("someHeaderValue", this.servletResponse.getHeader("someHeader"));
+ }
+
+
+ private ResponseEntity testException(Exception ex) {
+ ResponseEntity responseEntity = this.exceptionHandlerSupport.handleException(ex, this.request);
+ this.defaultExceptionResolver.resolveException(this.servletRequest, this.servletResponse, null, ex);
+
+ assertEquals(this.servletResponse.getStatus(), responseEntity.getStatusCode().value());
+
+ return responseEntity;
+ }
+
+
+ private static class TestController {
+ }
+
+ @ControllerAdvice
+ private static class ApplicationExceptionHandler extends ExceptionHandlerSupport {
+
+ @Override
+ protected Object handleServletRequestBindingException(ServletRequestBindingException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+
+ headers.set("someHeader", "someHeaderValue");
+ return "error content";
+ }
+
+
+ }
+
+}
diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml
index 33b2c895cb9..7868013ccb8 100644
--- a/src/reference/docbook/mvc.xml
+++ b/src/reference/docbook/mvc.xml
@@ -3646,12 +3646,20 @@ public String onSubmit(@RequestPart("meta-data") MetaData
is only a matter of implementing the
resolveException(Exception, Handler) method and
returning a ModelAndView , you may also use the provided
- SimpleMappingExceptionResolver . This resolver
+ SimpleMappingExceptionResolver or create
+ @ExceptionHandler methods.
+ The SimpleMappingExceptionResolver
enables you to take the class name of any exception that might be thrown
and map it to a view name. This is functionally equivalent to the
exception mapping feature from the Servlet API, but it is also possible
to implement more finely grained mappings of exceptions from different
- handlers.
+ handlers. The @ExceptionHandler annotation on
+ the other hand can be used on methods that should be invoked to handle an
+ exception. Such methods may be defined locally within an
+ @Controller or may apply globally to all
+ @RequestMapping methods when defined within
+ an @ControllerAdvice class.
+ The following sections explain this in more detail.
@@ -3659,36 +3667,44 @@ public String onSubmit(@RequestPart("meta-data") MetaData
The HandlerExceptionResolver interface
and the SimpleMappingExceptionResolver implementations
- allow you to map Exceptions to specific views along with some Java logic
- before forwarding to those views. However, in some cases, especially when
- working with programmatic clients (Ajax or non-browser) it is more
- convenient to set the status and optionally write error information to the
- response body.
-
- For that you can use @ExceptionHandler
- methods. When present within a controller such methods apply to exceptions
- raised by that contoroller or any of its sub-classes.
- Or you can also declare @ExceptionHandler
- methods in an @ControllerAdvice -annotated
- class and such methods apply to any controller.
- The @ControllerAdvice annotation is
- a component annotation allowing implementation classes to be autodetected
- through classpath scanning.
-
+ allow you to map Exceptions to specific views declaratively along with some
+ optional Java logic before forwarding to those views. However, in some cases,
+ especially when relying on @ResponseBody methods
+ rather than on view resolution, it may be more convenient to directly set the
+ status of the response and optionally write error content to the body of the
+ response.
- Here is an example with a controller-level
+ You can do that with @ExceptionHandler
+ methods. When declared within a controller such methods apply to exceptions
+ raised by @RequestMapping methods of that
+ contoroller (or any of its sub-classes). You can also declare an
+ @ExceptionHandler method within an
+ @ControllerAdvice class in which case it
+ handles exceptions from @RequestMapping
+ methods from any controller.
+ The @ControllerAdvice annotation is
+ a component annotation, which can be used with classpath scanning. It is
+ automatically enabled when using the MVC namespace and Java config, or
+ otherwise depending on whether the
+ ExceptionHandlerExceptionResolver is configured or not.
+ Below is an example of a controller-local
@ExceptionHandler method:
@Controller
public class SimpleController {
- // other controller method omitted
+
+ // @RequestMapping methods omitted ...
+
@ExceptionHandler(IOException.class)
- public ResponseEntity handleIOException(IOException ex) {
+ public ResponseEntity<String> handleIOException(IOException ex) {
+
// prepare responseEntity
+
return responseEntity;
}
+
}
The @ExceptionHandler value can be set to
@@ -3711,30 +3727,25 @@ public class SimpleController {
@ResponseBody to have the method return value
converted with message converters and written to the response stream.
- To better understand how @ExceptionHandler
- methods work, consider that in Spring MVC there is only one abstraction
- for handling exceptions and that's the
- HandlerExceptionResolver . There is a special
- implementation of that interface,
- the ExceptionHandlerExceptionResolver , which detects
- and invokes @ExceptionHandler methods.
- Handling of Spring MVC Exceptions
-
- Spring MVC may raise a number of exceptions while processing a request.
- A SimpleMappingExceptionResolver can be used to easily
- map any exception to a default error view or to more specific error views if
- desired. However when responding to programmatic clients you may prefer to
- translate specific exceptions to the appropriate status that indicates a
- client error (4xx) or a server error (5xx).
-
- For this reason Spring MVC provides the
- DefaultHandlerExceptionResolver , which translates specific
- Spring MVC exceptions by setting a specific response status code. By default,
- this resolver is registered by the DispatcherServlet .
- The following table describes some of the exceptions it handles:
+ Handling Standard Spring MVC Exceptions
+
+ Spring MVC may raise a number of exceptions while processing
+ a request. The SimpleMappingExceptionResolver can easily
+ map any exception to a default error view as needed.
+ However, when working with clients that interpret responses in an automated
+ way you will want to set specific status code on the response. Depending on
+ the exception raised the status code may indicate a client error (4xx) or a
+ server error (5xx).
+
+ The DefaultHandlerExceptionResolver translates
+ Spring MVC exceptions to specific error status codes. It is registered
+ by default with the MVC namespace, the MVC Java config. and also by the
+ the DispatcherServlet (i.e. when not using the MVC
+ namespace or Java config). Listed below are some of the exceptions handled
+ by this resolver and the corresponding status codes:
@@ -3822,38 +3833,19 @@ public class SimpleController {
- If you explicitly register one or more
- HandlerExceptionResolver instances in your configuration
- then the defaults registered by the DispatcherServlet are
- cancelled. This is standard behavior with regards to
- DispatcherServlet defaults.
- See for more details.
-
- If building a REST API, then it's very likely you will want to
- write some additional information about the error to the body of the response
- consistent with the API's error handling throughout. This includes the handling of
- Spring MVC exceptions, for which the DefaultHandlerExceptionResolver
- only sets the status code and doesn't assume how or what content should be written
- to the body.
-
- Instead you can create an @ControllerAdvice
- class that handles each of the exceptions handled by the
- DefaultHandlerExceptionResolver while also writing
- developer-friendly API error information to the response body consistent with
- the rest of all API error handling of the application. For example:
-
- @ControllerAdvice
-public class ApplicationExceptionResolver {
-
- @ExceptionHandler
- public ResponseEntity handleMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex) {
- MyApiError error = ... ;
- return new ResponseEntity(error, HttpStatus.SC_NOT_ACCEPTABLE);
- }
-
- // more @ExceptionHandler methods ...
-
-}
+ The DefaultHandlerExceptionResolver works
+ transparently by setting the status of the response. However, it stops short
+ of writing any error content to the body of the response while your
+ application may need to add developer-friendly content to every error
+ response for example when providing a REST API.
+
+ To achieve this extend ExceptionHandlerSupport ,
+ a convenient base class with an @ExceptionHandler
+ method that handles standard Spring MVC exceptions just as the
+ DefaultHandlerExceptionResolver does but also
+ allowing you to prepare error content for the body of the response.
+ See the Javadoc of ExceptionHandlerSupport
+ for more details.
@@ -3869,6 +3861,59 @@ public class ApplicationExceptionResolver {
+
+ Customizing the Default Servlet Container Error Page
+
+ When the status of the response is set to an error status code
+ and the body of the response is empty, Servlet containers commonly render
+ an HTML formatted error page.
+ To customize the default error page of the container, you can
+ declare an <error-page> element in
+ web.xml . Up until Servlet 3, that element had to
+ be mapped to a specific status code or exception type. Starting with
+ Servlet 3 an error page does not need to be mapped, which effectively
+ means the specified location customizes the default Servlet container
+ error page.
+
+ <error-page>
+ <location>/error</location>
+</error-page>
+
+
+ Note that the actual location for the error page can be a
+ JSP page or some other URL within the container including one handled
+ through an @Controller method:
+
+ When writing error information, the status code and the error message
+ set on the HttpServletResponse can be
+ accessed through request attributes in a controller:
+
+@Controller
+public class ErrorController {
+
+ @RequestMapping(value="/error", produces="application/json")
+ @ResponseBody
+ public Map<String, Object> handle(HttpServletRequest request) {
+
+ Map<String, Object> map = new HashMap<String, Object>();
+ map.put("status", request.getAttribute("javax.servlet.error.status_code"));
+ map.put("reason", request.getAttribute("javax.servlet.error.message"));
+
+ return map;
+ }
+
+}
+
+ or in a JSP:
+
+ <%@ page contentType="application/json" pageEncoding="UTF-8"%>
+{
+ status:<%=request.getAttribute("javax.servlet.error.status_code") %>,
+ reason:<%=request.getAttribute("javax.servlet.error.message") %>
+}
+
+
+