This content originally appeared on DEV Community and was authored by Abdulcelil Cercenazi
Typical Exception Handling In Java ☕️
It's a common thing in Java to try-catch parts of our code that we except to fail for some reason
- Missing files, corrupt data, etc...
try{
buggyMethod();
return "Done!";
}catch (RuntimeException e){
return "An error happened!";
}
Exception Handling In Spring ?
Let's view the workflow of Spring as a web framework:
- Listen to requests from the client.
- Take some actions based on our business logic.
- Return a response to the client containing the result of our work.
Now, we ideally want to catch any exception (error) that might arise at level 2 (action taking).
We can write a try catch block at every controller method that handles the exceptions in a standard way ??
@RestController
@RequiredArgsConstructor
public class TestController
{
private final ExceptionHandler exceptionHandler;
@GetMapping("/test1")
public void test1(){
try{
// test 1 things
}catch (Exception e){
exceptionHandler.handleException(e);
}
}
@GetMapping("/test2")
public void test2(){
try{
// test 2 things
}catch (Exception e){
exceptionHandler.handleException(e);
}
}
}
?? The problem with this approach however is that it get quite tedious when we have many more controller methods.
Why capture all exceptions? and not just let them occur???
- We want our application to be user friendly and handle all edge cases, thus we want it to return responses with standard format.
- We might also want to log those exceptions in a backlog to get back to them and investigate them, or do whatever we like with them.
@ControllerAdvice To The Rescue??
The idea is that we declare a method that will handle any unhandled exceptions in the application.
How to do it? ?
First, we need to declare a class and annotate it with @ControllerAdvice
.
Then, we declare methods, each handling a class of exception.
@ControllerAdvice @Slf4j
public class GlobalErrorHandler
{
@ResponseStatus(INTERNAL_SERVER_ERROR)
@ResponseBody
@ExceptionHandler(Exception.class)
public String methodArgumentNotValidException(Exception ex) {
// you can take actions based on the exception
log.error("An unexpected error has happened", ex);
return "An internal error has happened, please report the incident";
}
@ResponseStatus(BAD_REQUEST)
@ResponseBody
@ExceptionHandler(InvalidParameterException.class)
public String invalidParameterException(InvalidParameterException ex){
return "This is a BAD REQUEST";
}
}
What does the above code do?☝️
- Declares two methods that will be run whenever an exception of class
Exception
,InvalidParameterException
(or subclass of them) is thrown and not handled locally in their thread of execution. - They return a string response back the client.
Note that we can specify more than one handler in the class annotated with @ControllerAdvice
.
Now, let's code some endpoints for us to validate against. Let's code three endpoints
- One that handles the exception thrown.
- The other two leave the handling to the global exception handler
@RestController @RequiredArgsConstructor
public class TestController
{
@GetMapping("/buggyMethod")
public String testMeWithExceptionHandler(){
try{
buggyMethod();
return "Done!";
}catch (RuntimeException e){
return "An error happened!";
}
}
@GetMapping("/potentialBuggyMethod")
public String testMeWithoutExceptionHandler(){
undercoverBuggyMethod();
return "Done!";
}
@PostMapping("/invalidParamMethod")
public String testForInvalidParam(){
buggyParameters();
return "Done";
}
private void buggyMethod(){
throw new RuntimeException();
}
private void undercoverBuggyMethod(){
throw new RuntimeException("oops");
}
private void buggyParameters(){
throw new InvalidParameterException();
}
}
Let's Verify It With Some Tests ?
@WebMvcTest(controllers = TestController.class)
public class GlobalExceptionHandlerTest
{
@Autowired
private MockMvc mockMvc;
@Test
public void givenAGetRequestToBuggyEndPoint_DetectErrorMessage() throws Exception
{
MvcResult mvcResult = mockMvc
.perform(get("/buggyMethod"))
.andExpect(status().isOk())
.andReturn();
String response = mvcResult.getResponse().getContentAsString();
assertEquals(response, "An error happened!");
}
@Test
public void givenAGetRequestToPotentialBuggyMethod_DetectErrorMessage() throws Exception
{
MvcResult mvcResult = mockMvc
.perform(get("/potentialBuggyMethod"))
.andExpect(status().is5xxServerError())
.andReturn();
String response = mvcResult.getResponse().getContentAsString();
assertEquals(response, "An internal error has happened, please report the incident");
}
@Test
public void givenAPostRequestToBuggyMethod_DetectInvalidParameterErrorMessage() throws Exception
{
MvcResult mvcResult = mockMvc
.perform(post("/invalidParamMethod"))
.andExpect(status().isBadRequest())
.andReturn();
String response = mvcResult.getResponse().getContentAsString();
assertEquals(response, "This is a BAD REQUEST");
}
}
Conclusion ?
Unexpected and general errors should be handled elegantly to sustain a smooth experience for our application clients. This is best done using Spring's ControllerAdvice.
Check this article for more details Error Handling for REST with Spring?
Check the code on GitHub?
This content originally appeared on DEV Community and was authored by Abdulcelil Cercenazi
Abdulcelil Cercenazi | Sciencx (2021-08-04T19:42:00+00:00) Handle Spring Exceptions Like A Pro. Retrieved from https://www.scien.cx/2021/08/04/handle-spring-exceptions-like-a-pro/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.