RestControllerAdvice: how to handle exceptions in Spring Boot
- @ControllerAdvice and @RestControllerAdvice, when to use?
- Example of RestControllerAdvice
- How to unit test a RestControllerAdvice
@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
.