Handle Spring Exceptions Like A Pro

Abdulcelil Cercenazi - Aug 4 '21 - - Dev Community

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!";  
}
Enter fullscreen mode Exit fullscreen mode

Exception Handling In Spring 🍃

Let's view the workflow of Spring as a web framework:

  1. Listen to requests from the client.
  2. Take some actions based on our business logic.
  3. 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);  
      }  
    }
}
Enter fullscreen mode Exit fullscreen mode

👎🏽 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";  
   }  
}
Enter fullscreen mode Exit fullscreen mode

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();  
    } 
}
Enter fullscreen mode Exit fullscreen mode

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");  
    }  
}
Enter fullscreen mode Exit fullscreen mode

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🥷

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player