docs
Annotations and classes for handling exceptions in Spring MVC applications, mapping exceptions to HTTP responses, and providing global error handling advice across controllers.
Marks a method to handle specific exceptions thrown by controller methods. Can be used within a controller for local exception handling or within @ControllerAdvice for global exception handling.
/**
* Annotation for handling exceptions in specific handler classes and/or handler methods.
*
* Handler methods which are annotated with this annotation are allowed to have very flexible
* signatures. They may have parameters of the following types, in arbitrary order:
* - An exception argument: declared as a general Exception or as a more specific exception.
* - Request and/or response objects (typically from the Servlet API).
* - Session object: typically HttpSession.
* - WebRequest or NativeWebRequest.
* - Locale for the current request locale.
* - InputStream/Reader for access to the request's content.
* - OutputStream/Writer for generating the response's content.
* - Model as an alternative to returning a model map from the handler method.
*
* Handler methods can return:
* - A ModelAndView object (from Servlet MVC).
* - A Model object, with the view name implicitly determined through a RequestToViewNameTranslator.
* - A Map object for exposing a model, with the view name implicitly determined through a RequestToViewNameTranslator.
* - A View object.
* - A String value which is interpreted as view name.
* - @ResponseBody annotated methods (Servlet-only) to set the response content.
* - A ResponseEntity object to set response headers and content.
* - void if the method handles the response itself.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
/**
* Exceptions handled by the annotated method. If empty, will default to any
* exceptions listed in the method argument list.
*/
Class<? extends Throwable>[] value() default {};
}Usage Example:
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}
// Local exception handler for this controller
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<ErrorResponse> handleProductNotFound(ProductNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
"PRODUCT_NOT_FOUND",
ex.getMessage(),
HttpStatus.NOT_FOUND.value()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgument(IllegalArgumentException ex) {
ErrorResponse error = new ErrorResponse(
"INVALID_ARGUMENT",
ex.getMessage(),
HttpStatus.BAD_REQUEST.value()
);
return ResponseEntity.badRequest().body(error);
}
// Handle multiple exception types
@ExceptionHandler({ProductOutOfStockException.class, ProductDiscontinuedException.class})
public ResponseEntity<ErrorResponse> handleProductUnavailable(RuntimeException ex) {
ErrorResponse error = new ErrorResponse(
"PRODUCT_UNAVAILABLE",
ex.getMessage(),
HttpStatus.CONFLICT.value()
);
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
// Access request information in exception handler
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handleValidation(
ValidationException ex,
WebRequest request) {
String path = request.getDescription(false);
ErrorResponse error = new ErrorResponse(
"VALIDATION_FAILED",
ex.getMessage(),
HttpStatus.BAD_REQUEST.value(),
path
);
return ResponseEntity.badRequest().body(error);
}
}Marks a class with global @ExceptionHandler, @InitBinder, or @ModelAttribute methods that apply to multiple controllers. Supports selective application to specific controllers, packages, or annotations.
/**
* Specialization of @Component for classes that declare @ExceptionHandler, @InitBinder,
* or @ModelAttribute methods to be shared across multiple @Controller classes.
*
* Classes annotated with @ControllerAdvice are auto-detected through classpath scanning
* if configured as Spring beans.
*
* @ControllerAdvice is typically used in combination with @ExceptionHandler methods
* for global error handling in the web tier.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
/**
* Alias for {@link #basePackages}.
* Allows for more concise annotation declarations.
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* Array of base packages.
* Controllers that belong to those base packages or sub-packages thereof
* will be included, e.g.: "org.example.web".
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages} for specifying the packages
* to select controllers to be advised by the @ControllerAdvice annotated class.
* The package of each class specified will be used.
*/
Class<?>[] basePackageClasses() default {};
/**
* Array of classes.
* Controllers that are assignable to at least one of the given types
* will be advised by the @ControllerAdvice annotated class.
*/
Class<?>[] assignableTypes() default {};
/**
* Array of annotations.
* Controllers that are annotated with at least one of the supplied annotations
* will be advised by the @ControllerAdvice annotated class.
* Consider creating a custom composed annotation or use multiple
* @ControllerAdvice declarations to target specific groups of controllers.
*/
Class<? extends Annotation>[] annotations() default {};
}Usage Example:
// Global exception handler for all controllers
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
"RESOURCE_NOT_FOUND",
ex.getMessage(),
HttpStatus.NOT_FOUND.value()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
ErrorResponse error = new ErrorResponse(
"INTERNAL_ERROR",
"An unexpected error occurred",
HttpStatus.INTERNAL_SERVER_ERROR.value()
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationErrorResponse> handleValidationErrors(
MethodArgumentNotValidException ex) {
List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
ValidationErrorResponse error = new ValidationErrorResponse(
"VALIDATION_FAILED",
fieldErrors
);
return ResponseEntity.badRequest().body(error);
}
}
// Controller advice for specific package
@ControllerAdvice(basePackages = "com.example.api.admin")
public class AdminControllerAdvice {
@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<ErrorResponse> handleUnauthorized(UnauthorizedException ex) {
ErrorResponse error = new ErrorResponse(
"UNAUTHORIZED",
"Admin access required",
HttpStatus.UNAUTHORIZED.value()
);
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
}
}
// Controller advice for specific controller types
@ControllerAdvice(assignableTypes = {UserController.class, AccountController.class})
public class UserAccountAdvice {
@ModelAttribute("currentUser")
public User getCurrentUser() {
return securityService.getCurrentUser();
}
@ExceptionHandler(AccountLockedException.class)
public ResponseEntity<ErrorResponse> handleAccountLocked(AccountLockedException ex) {
ErrorResponse error = new ErrorResponse(
"ACCOUNT_LOCKED",
ex.getMessage(),
HttpStatus.FORBIDDEN.value()
);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
}
}
// Controller advice for REST controllers only
@ControllerAdvice(annotations = RestController.class)
public class RestControllerAdvice {
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleInvalidJson(HttpMessageNotReadableException ex) {
ErrorResponse error = new ErrorResponse(
"INVALID_JSON",
"Invalid JSON in request body",
HttpStatus.BAD_REQUEST.value()
);
return ResponseEntity.badRequest().body(error);
}
}Convenience annotation that combines @ControllerAdvice and @ResponseBody. All exception handler methods automatically serialize their return values to the response body.
/**
* A convenience annotation that is itself annotated with @ControllerAdvice and @ResponseBody.
*
* Types that carry this annotation are treated as controller advice where @ExceptionHandler
* methods assume @ResponseBody semantics by default.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
/**
* Alias for {@link ControllerAdvice#basePackages}.
*/
@AliasFor(annotation = ControllerAdvice.class)
String[] value() default {};
/**
* Alias for {@link ControllerAdvice#basePackages}.
*/
@AliasFor(annotation = ControllerAdvice.class)
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages} for specifying the packages
* to select controllers to be advised.
*/
@AliasFor(annotation = ControllerAdvice.class)
Class<?>[] basePackageClasses() default {};
/**
* Array of classes. Controllers that are assignable to at least one of the
* given types will be advised.
*/
@AliasFor(annotation = ControllerAdvice.class)
Class<?>[] assignableTypes() default {};
/**
* Array of annotations. Controllers that are annotated with at least one of
* the supplied annotations will be advised.
*/
@AliasFor(annotation = ControllerAdvice.class)
Class<? extends Annotation>[] annotations() default {};
}Usage Example:
@RestControllerAdvice
public class RestExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(RestExceptionHandler.class);
@ExceptionHandler(ResourceNotFoundException.class)
public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) {
logger.warn("Resource not found: {}", ex.getMessage());
return new ErrorResponse(
"RESOURCE_NOT_FOUND",
ex.getMessage(),
HttpStatus.NOT_FOUND.value(),
Instant.now()
);
}
@ExceptionHandler(DataIntegrityViolationException.class)
public ErrorResponse handleDataIntegrityViolation(DataIntegrityViolationException ex) {
logger.error("Data integrity violation", ex);
return new ErrorResponse(
"DATA_INTEGRITY_VIOLATION",
"Data constraint violation occurred",
HttpStatus.CONFLICT.value(),
Instant.now()
);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ValidationErrorResponse handleValidationErrors(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return new ValidationErrorResponse(
"VALIDATION_FAILED",
errors,
HttpStatus.BAD_REQUEST.value()
);
}
}
// REST controller advice for specific base package
@RestControllerAdvice(basePackages = "com.example.api")
public class ApiExceptionHandler {
@ExceptionHandler(ApiException.class)
public ResponseEntity<ApiErrorResponse> handleApiException(ApiException ex) {
ApiErrorResponse error = new ApiErrorResponse(
ex.getErrorCode(),
ex.getMessage(),
ex.getDetails()
);
return ResponseEntity.status(ex.getStatus()).body(error);
}
}Base class for @ControllerAdvice classes that provides centralized exception handling for standard Spring MVC exceptions. Provides protected methods to customize the response for each exception type.
/**
* A convenient base class for @ControllerAdvice classes that wish to provide
* centralized exception handling across all @RequestMapping methods through
* @ExceptionHandler methods.
*
* This base class provides an @ExceptionHandler method for handling internal
* Spring MVC exceptions. This method returns a ResponseEntity for writing to
* the response with a MessageConverter, in contrast to
* DefaultHandlerExceptionResolver which returns a ModelAndView.
*
* If there is no need to write error content to the response body, or when
* using view resolution (e.g. via ContentNegotiatingViewResolver), then
* DefaultHandlerExceptionResolver is good enough.
*/
public abstract class ResponseEntityExceptionHandler {
/**
* A logger available to subclasses.
*/
protected final Log logger = LogFactory.getLog(getClass());
/**
* Provides handling for standard Spring MVC exceptions.
*
* @param ex the target exception
* @param request the current request
* @return a ResponseEntity instance
*/
@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
})
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {}
/**
* Customize the response for HttpRequestMethodNotSupportedException.
* This method delegates to handleExceptionInternal.
*
* @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 a ResponseEntity instance
*/
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(
HttpRequestMethodNotSupportedException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {}
/**
* Customize the response for HttpMediaTypeNotSupportedException.
*
* @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 a ResponseEntity instance
*/
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(
HttpMediaTypeNotSupportedException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {}
/**
* Customize the response for HttpMediaTypeNotAcceptableException.
*
* @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 a ResponseEntity instance
*/
protected ResponseEntity<Object> handleHttpMediaTypeNotAcceptable(
HttpMediaTypeNotAcceptableException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {}
/**
* Customize the response for MissingPathVariableException.
*
* @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 a ResponseEntity instance
*/
protected ResponseEntity<Object> handleMissingPathVariable(
MissingPathVariableException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {}
/**
* Customize the response for MissingServletRequestParameterException.
*
* @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 a ResponseEntity instance
*/
protected ResponseEntity<Object> handleMissingServletRequestParameter(
MissingServletRequestParameterException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {}
/**
* Customize the response for MethodArgumentNotValidException.
* This method delegates to handleExceptionInternal.
*
* @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 a ResponseEntity instance
*/
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {}
/**
* Customize the response for BindException.
*
* @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 a ResponseEntity instance
*/
protected ResponseEntity<Object> handleBindException(
BindException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {}
/**
* Customize the response for TypeMismatchException.
*
* @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 a ResponseEntity instance
*/
protected ResponseEntity<Object> handleTypeMismatch(
TypeMismatchException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {}
/**
* Customize the response for HttpMessageNotReadableException.
*
* @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 a ResponseEntity instance
*/
protected ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {}
/**
* Customize the response for HttpMessageNotWritableException.
*
* @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 a ResponseEntity instance
*/
protected ResponseEntity<Object> handleHttpMessageNotWritable(
HttpMessageNotWritableException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {}
/**
* Customize the response for NoHandlerFoundException.
*
* @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 a ResponseEntity instance
*/
protected ResponseEntity<Object> handleNoHandlerFoundException(
NoHandlerFoundException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {}
/**
* Customize the response for AsyncRequestTimeoutException.
*
* @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 a ResponseEntity instance
*/
protected ResponseEntity<Object> handleAsyncRequestTimeoutException(
AsyncRequestTimeoutException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {}
/**
* A single place to customize the response body of all exception types.
* The default implementation sets the WebUtils.ERROR_EXCEPTION_ATTRIBUTE
* request attribute and creates a ResponseEntity from the given body,
* headers, and status.
*
* @param ex the exception
* @param body the body for the response
* @param headers the headers for the response
* @param statusCode the response status
* @param request the current request
* @return a ResponseEntity instance
*/
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex,
Object body,
HttpHeaders headers,
HttpStatusCode statusCode,
WebRequest request) {}
/**
* Create the ResponseEntity to use from the given body, headers, and statusCode.
* Subclasses can override this method to provide a custom body.
*
* @param body the body to use for the response (may be null)
* @param headers the headers for the response
* @param statusCode the status code for the response
* @param request the current request
* @return the created ResponseEntity
*/
protected ResponseEntity<Object> createResponseEntity(
Object body,
HttpHeaders headers,
HttpStatusCode statusCode,
WebRequest request) {}
}Usage Example:
@RestControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
// Override to customize handling of validation errors
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
ValidationErrorResponse body = new ValidationErrorResponse(
"VALIDATION_FAILED",
errors,
status.value()
);
return handleExceptionInternal(ex, body, headers, status, request);
}
// Override to customize handling of missing parameters
@Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(
MissingServletRequestParameterException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {
ErrorResponse body = new ErrorResponse(
"MISSING_PARAMETER",
String.format("Required parameter '%s' is missing", ex.getParameterName()),
status.value()
);
return handleExceptionInternal(ex, body, headers, status, request);
}
// Override to customize handling of unsupported media type
@Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(
HttpMediaTypeNotSupportedException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {
StringBuilder builder = new StringBuilder();
builder.append(ex.getContentType());
builder.append(" media type is not supported. Supported media types are ");
ex.getSupportedMediaTypes().forEach(t -> builder.append(t).append(", "));
ErrorResponse body = new ErrorResponse(
"UNSUPPORTED_MEDIA_TYPE",
builder.substring(0, builder.length() - 2),
status.value()
);
return handleExceptionInternal(ex, body, headers, status, request);
}
// Handle custom application exceptions
@ExceptionHandler(BusinessException.class)
public ResponseEntity<Object> handleBusinessException(
BusinessException ex,
WebRequest request) {
ErrorResponse body = new ErrorResponse(
ex.getErrorCode(),
ex.getMessage(),
HttpStatus.UNPROCESSABLE_ENTITY.value()
);
return handleExceptionInternal(
ex,
body,
new HttpHeaders(),
HttpStatus.UNPROCESSABLE_ENTITY,
request
);
}
// Override to customize all exception responses
@Override
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex,
Object body,
HttpHeaders headers,
HttpStatusCode statusCode,
WebRequest request) {
// Log the exception
logger.error("Exception occurred", ex);
// If body is null, create a default error response
if (body == null) {
body = new ErrorResponse(
"ERROR",
ex.getMessage(),
statusCode.value()
);
}
return super.handleExceptionInternal(ex, body, headers, statusCode, request);
}
}Standard exceptions handled by Spring MVC exception handling infrastructure.
/**
* Exception thrown when a request handler does not support a specific request method.
*/
public class HttpRequestMethodNotSupportedException extends ServletException {
public HttpRequestMethodNotSupportedException(String method) {}
public HttpRequestMethodNotSupportedException(String method, String msg) {}
public HttpRequestMethodNotSupportedException(String method, Collection<String> supportedMethods) {}
public String getMethod() {}
public String[] getSupportedMethods() {}
}
/**
* Exception thrown when a client POSTs, PUTs, or PATCHes content of a type not supported by request handler.
*/
public class HttpMediaTypeNotSupportedException extends ServletException {
public HttpMediaTypeNotSupportedException(String msg) {}
public HttpMediaTypeNotSupportedException(MediaType contentType, List<MediaType> supportedMediaTypes) {}
public MediaType getContentType() {}
public List<MediaType> getSupportedMediaTypes() {}
}
/**
* Exception thrown when the request handler cannot generate a response that is acceptable by the client.
*/
public class HttpMediaTypeNotAcceptableException extends ServletException {
public HttpMediaTypeNotAcceptableException(String message) {}
public HttpMediaTypeNotAcceptableException(List<MediaType> supportedMediaTypes) {}
public List<MediaType> getSupportedMediaTypes() {}
}
/**
* Exception thrown when a required path variable is missing.
*/
public class MissingPathVariableException extends ServletException {
public MissingPathVariableException(String variableName, MethodParameter parameter) {}
public String getVariableName() {}
public MethodParameter getParameter() {}
}
/**
* Exception thrown when a required request parameter is missing.
*/
public class MissingServletRequestParameterException extends ServletException {
public MissingServletRequestParameterException(String parameterName, String parameterType) {}
public String getParameterName() {}
public String getParameterType() {}
}
/**
* Exception thrown when validation on an argument annotated with @Valid fails.
*/
public class MethodArgumentNotValidException extends BindException {
public MethodArgumentNotValidException(MethodParameter parameter, BindingResult bindingResult) {}
public MethodParameter getParameter() {}
}
/**
* Exception thrown when the request is missing a part in a multipart request.
*/
public class MissingServletRequestPartException extends ServletException {
public MissingServletRequestPartException(String partName) {}
public String getRequestPartName() {}
}
/**
* Exception to be thrown when no handler is found for a request.
*/
public class NoHandlerFoundException extends ServletException {
public NoHandlerFoundException(String httpMethod, String requestURL, HttpHeaders headers) {}
public String getHttpMethod() {}
public String getRequestURL() {}
public HttpHeaders getHeaders() {}
}
/**
* Exception thrown when an async request times out.
*/
public class AsyncRequestTimeoutException extends RuntimeException {
public AsyncRequestTimeoutException() {}
}