Master Spring MVC Exception Handling: Local, Global, and REST API Strategies
This article explains how to use @ExceptionHandler with @Controller, @ControllerAdvice, and @RestControllerAdvice for local and global exception handling in Spring MVC, details supported method parameters and return types, and shows how to customize REST API error responses by extending ResponseEntityExceptionHandler.
Environment: Spring 5.3.23
Review
@Controller and @ControllerAdvice can use @ExceptionHandler to handle controller method exceptions, as shown below:
Local exception handling
<code>@Controller
public class SimpleController {
// ...
// Can only handle exceptions occurring in this SimpleController
@ExceptionHandler
public ResponseEntity<String> handle(Exception ex) {
// ...
}
}
</code>Global exception handling
<code>@RestControllerAdvice
public class TestControllerAdvice {
// Global exception handling
@ExceptionHandler
public Object handle(Exception e) {
return "Global exception: " + e.getMessage();
}
}
</code>Specify exception types
<code>@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
// ...
}
</code>In @ExceptionHandler you specify which exception classes it can handle.
Exception handler parameters
@ExceptionHandler methods support the following parameters:
Exception type : Used to access the exception that was thrown.
HandlerMethod : Used to access the controller method that threw the exception.
WebRequest, NativeWebRequest : General access to request parameters, request and session attributes without using the Servlet API directly.
javax.servlet.ServletRequest, javax.servlet.ServletResponse : Choose any specific request or response type (e.g., ServletRequest, HttpServletRequest, Spring's MultipartRequest, MultipartHttpServletRequest).
HttpSession : Forces the existence of a session; never null. Note that session access is not thread‑safe; consider setting the synchronizeOnSession flag to true on RequestMappingHandlerAdapter for concurrent access.
java.security.Principal : The currently authenticated user—if known, may be a specific Principal implementation.
HttpMethod : The HTTP method of the request.
java.util.Locale : The current request locale resolved by the most specific LocaleResolver or LocaleContextResolver.
java.util.TimeZone, java.time.ZoneId : The time zone associated with the current request, determined by LocaleContextResolver.
java.io.OutputStream, java.io.Writer : Access to the raw response body exposed by the Servlet API.
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap : Access to the model for the error response; always empty.
RedirectAttributes : Attributes used in a redirect (appended to the query string) and temporary flash attributes stored until the next request.
@SessionAttribute : Access to any session attribute, as opposed to model attributes stored in the session via @SessionAttributes.
@RequestAttribute : Access to request attributes.
Exception handler return values
@ExceptionHandler methods support the following return values:
@ResponseBody : The return value is converted by an HttpMessageConverter and written to the response.
HttpEntity<B>, ResponseEntity<B> : The return value specifies a full response (headers and body) that is converted by an HttpMessageConverter.
String : View name resolved by a ViewResolver and used with the implicit model.
View : A View instance used to render the implicit model.
java.util.Map, org.springframework.ui.Model : Attributes added to the implicit model; view name is determined implicitly by RequestToViewNameTranslator.
@ModelAttribute : Attributes added to the model; view name is determined implicitly. Note that @ModelAttribute is optional.
ModelAndView : The view and model attributes (and optional response status) to use.
void : Considered to have fully handled the response if the method also has ServletResponse, OutputStream parameters or @ResponseStatus. Otherwise, void can represent a “no response body” for REST controllers or default view name selection for HTML controllers.
Any other return value : If it does not match any of the above and is not a simple type, it is treated as a model attribute to be added to the model; simple types remain unresolved.
REST API Exceptions
A common requirement for REST services is to include error details in the response body. Spring does not do this automatically; the details are application‑specific. A @RestController can use an @ExceptionHandler method that returns a ResponseEntity to set the status and body. Such methods can also be declared in a @ControllerAdvice class for global application.
To implement global exception handling with error details, extend ResponseEntityExceptionHandler, which provides handling for Spring MVC‑thrown exceptions and hooks for custom response bodies. Create a subclass annotated with @ControllerAdvice and override the necessary methods, for example:
<code>@RestControllerAdvice
static class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
System.out.println(">>>>>>>>>> - body - " + body);
System.out.println(">>>>>>>>>> - headers - " + headers);
System.out.println(">>>>>>>>>> - status - " + status);
return new ResponseEntity<>(ex.getMessage(), headers, status);
}
}
</code>ResponseEntityExceptionHandler already defines handlers for many exception types, such as HttpRequestMethodNotSupportedException, HttpMediaTypeNotSupportedException, MissingPathVariableException, etc.
<code>public abstract class ResponseEntityExceptionHandler {
@ExceptionHandler({
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
MissingServletRequestPartException.class,
BindException.class,
NoHandlerFoundException.class,
AsyncRequestTimeoutException.class
})
@Nullable
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
HttpHeaders headers = new HttpHeaders();
if (ex instanceof HttpRequestMethodNotSupportedException) {
HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
} else if (ex instanceof HttpMediaTypeNotSupportedException) {
HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request);
} // ...
else {
throw ex;
}
}
}
</code>Done!!!
Follow me so I don't get lost.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.