RestControllerAdvice: how to handle exceptions in Spring Boot

Updated:

@ControllerAdvice and @RestControllerAdvice, when to use?

ControllerAdvice and RestControllerAdvice allow your program to intercept exceptions in your code and execute some code when these are thrown.

ControllerAdvice is a specialization of @Component.

By default, the methods in ControllerAdvice apply to all the controllers. @RestControllerAdvice is a convenience annotation that includes @ControllerAdvice and @ResponseBody, allowing you to answer directly with an HTTP response.

The advantage of this feature is to have a centralized management of the exceptions inside your application.

Example of RestControllerAdvice

@RestControllerAdvice 
public class MyControllerAdvice { 
    // this handler catches the general Exception ... 
    @ExceptionHandler(Exception.class) 
    // ... and returns an http 500 'Internal Server Error' to the client 
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 
    ErrorResponse genericException(Exception ex){ 
        // we can add some more information for the client 
        return getErrorResponse(ex,"An exception has been triggered on the server, please contact the administrator"); 
    } 
 
    @ExceptionHandler(EntityNotFoundException.class) 
    @ResponseStatus(HttpStatus.NOT_FOUND) 
    ErrorResponse entityNotFoundHandler(EntityNotFoundException ex){ 
        return getErrorResponse(ex, "No item found."); 
    } 
} 

In this example MyControllerAdvice catches 2 exceptions that could be thrown by controllers, Exception and EntityNotFoundException. The last could come from a Database request.

In the case of Exception an HTTP 500 is returned to the web client with the message An exception has been triggered on the server ...,
in the case of EntityNotFoundException an HTTP 404 is sent with the message No item found..

In the examples we decorated the ErrorResponse in a ResponseEntity<ErrorResponse> because in the real code we have multiple and more complex situations.

private ResonseEntity<ErrorResponse>(Throwable t, String message) { 
  return new ErrorResponse(message) 
} 

How to unit test a RestControllerAdvice

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder.get; 
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 
 
@TestInstance(TestInstance.Lifecycle.PER_CLASS) 
class ExampleRestControllerAdviceTest { 
  private MockMvc mockMvc; 
 
  @BeforeAll() 
  void init() { 
      mockMvc = MockMvcBuilders.standaloneSetup(new TestController()) 
              .setControllerAdvice(DefaultRestControllerAdvice.class).build(); 
  } 
   
  @Test 
  void exceptionTest() throws Exception { 
    mockMvc.perform("/exception") 
    .andExpect(status().is5xxServerError()) 
    .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Unexpected error occurred. Please contact the administrator.")); 
  } 
} 

We built one unit test to show how easy is to test the controller advice, the tricky part is to create a MockMvc instance using .standaloneSetup that register our @Controller and set the advice to this instance with setControllerAdvice.


Fullstack Angular / Java application quick start guide.
WebApp built by Marco using SpringBoot 3.2.4 and Java 21. Hosted in Switzerland (GE8).