Mastering Spring REST API Exception Handling: From @ExceptionHandler to @ControllerAdvice

This guide walks through multiple strategies for handling exceptions in Spring REST APIs, covering legacy @ExceptionHandler methods, HandlerExceptionResolver options, the modern @ControllerAdvice approach, and security-related error handling, providing code examples and best‑practice recommendations for building robust, maintainable services.

Programmer DD
Programmer DD
Programmer DD
Mastering Spring REST API Exception Handling: From @ExceptionHandler to @ControllerAdvice

Overview

This article demonstrates how to implement exception handling for REST APIs using Spring. It examines solutions recommended for Spring 3.2 and later versions, as well as older approaches, emphasizing separation of concerns and the ability to throw exceptions from the application code.

Solution 1 – Controller‑scoped @ExceptionHandler

The first approach defines a method inside a @Controller and annotates it with @ExceptionHandler. This method handles specific exception types only for that controller, which limits its reuse across the application.

public class FooController {
    // ...
    @ExceptionHandler({CustomException1.class, CustomException2.class})
    public void handleException() {
        // handling logic
    }
}

Because the handler is tied to a single controller, developers often create a base controller class that all controllers extend, but this is problematic when some controllers cannot inherit from the base class.

Solution 2 – HandlerExceptionResolver

A HandlerExceptionResolver can process any exception thrown by the application and produce a unified response body. Spring provides several built‑in resolvers:

ExceptionHandlerExceptionResolver – core of the @ExceptionHandler mechanism, introduced in Spring 3.1.

DefaultHandlerExceptionResolver – maps standard Spring exceptions to HTTP status codes (4xx, 5xx) but does not modify the response body.

ResponseStatusExceptionResolver – uses the @ResponseStatus annotation on custom exceptions to set the HTTP status.

Custom resolvers can be created to control both status and body. Example of a custom resolver:

@Component
public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {
    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
            Object handler, Exception ex) {
        try {
            if (ex instanceof IllegalArgumentException) {
                return handleIllegalArgument((IllegalArgumentException) ex, response, handler);
            }
        } catch (Exception handlerException) {
            logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
        }
        return null;
    }

    private ModelAndView handleIllegalArgument(IllegalArgumentException ex,
            HttpServletResponse response) throws IOException {
        response.sendError(HttpServletResponse.SC_CONFLICT);
        // additional logic to write a JSON or XML body based on the Accept header
        return new ModelAndView();
    }
}

This resolver checks the exception type, writes an appropriate HTTP status, and can produce a response body respecting the client’s Accept header.

Solution 3 – Global @ControllerAdvice (Spring 3.2+)

Spring 3.2 introduced @ControllerAdvice, which allows global exception handling without tying handlers to a specific controller. It works with ResponseEntity for type‑safe, flexible responses.

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
    @ExceptionHandler({IllegalArgumentException.class, IllegalStateException.class})
    protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
        String bodyOfResponse = "This should be application specific";
        return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request);
    }
}

The annotated class aggregates multiple exception handlers into a single component, giving full control over response bodies and status codes.

Handling Access Denied in Spring Security

When an authenticated user lacks sufficient permissions, Spring Security can redirect to a custom error page or invoke a custom AccessDeniedHandler.

Custom error page (XML configuration)

<http>
    <intercept-url pattern="/admin/*" access="hasAnyRole('ROLE_ADMIN')"/>
    <access-denied-handler error-page="/my-error-page"/>
</http>

Custom AccessDeniedHandler (Java configuration)

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException ex) throws IOException, ServletException {
        response.sendRedirect("/my-error-page");
    }
}

In a security configuration class, the custom handler can be wired:

@Autowired
private CustomAccessDeniedHandler accessDeniedHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/admin/*").hasAnyRole("ROLE_ADMIN")
        .and()
        .exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}

Method‑level security

Exceptions such as AccessDeniedException thrown by @PreAuthorize, @PostAuthorize, or @Secure can be handled by the same global @ControllerAdvice:

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<Object> handleAccessDeniedException(Exception ex, WebRequest request) {
        return new ResponseEntity<>("Access denied message here", new HttpHeaders(), HttpStatus.FORBIDDEN);
    }
}

Conclusion

The tutorial covered several ways to implement exception handling in Spring REST services, starting with legacy mechanisms and progressing to the modern @ControllerAdvice approach available in Spring 3.2 and later versions. The provided code samples can be cloned from the accompanying GitHub repository and run as a Maven project.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaException HandlingREST API
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.