DEV Community

loading...
Cover image for Handle Spring Exceptions Like A Pro

Handle Spring Exceptions Like A Pro

Abdulcelil Cercenazi
I enjoy learning and teaching, I am currently working as a Software developer using Spring and Vanilla JavaScript.
・3 min read

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🥷

Discussion (10)

Collapse
mi_native_nutt profile image
Mark Nuttall

This is a way to do it. The best? Probably not. First spring typically handles most errors in a generic way that is good enough for almost all clients. Most clients are only going to care about failure or security or success. If not the actual best way to do it is to throw a custom exception and annotate that exception so it will throw the desired HTTP code. That way you have a custom exception which is the right thing to do and then handle them in a specific way versus a somewhat less generic way via the controller advice

Collapse
alwynschoeman profile image
Alwyn Schoeman

I agree for internal facing applications. For external you don't want the stacktrace to be sent to the client since it allows would be hackers to gain insight into the inner workings of your application.

Collapse
mi_native_nutt profile image
Mark Nuttall

See that's the thing about springboat there's always a way to handle that via configuration. There is a one line configuration to turn that off and you can do it by environment with profiles

Collapse
gorynych profile image
Gorynych

Setting HTTP code for you own exceptions - is the best way to high coupled monolith with mixed everything together. "The best? Probably not." )

Collapse
jarjanazy profile image
Abdulcelil Cercenazi Author

Thanks for the feedback :) yeah you are right, however I've found that controller advice can also be like a final fishnet for any unexpected events.

Collapse
mi_native_nutt profile image
Mark Nuttall

Yeah I understand what you're saying but I've got about 40 spring boot applications and I've never needed it. The default spring boot mechanism handles the majority of unexpected events.

Thread Thread
jarjanazy profile image
Abdulcelil Cercenazi Author

I have used it :D so it really depends on your work style

Thread Thread
mi_native_nutt profile image
Mark Nuttall

Well it's not really work style it's really about if you're using spring boot techniques or Reinventing the wheel. But I mean if that's what you want to do go for it LOL

Collapse
gorynych profile image
Gorynych

Using of @ControllerAdvice as an idea - it's ok. But the problem of Spring' implementation is that there could be only one @ControllerAdvice with @ExceptionHandler ((. So, this class become really huge (from the responsibility point view, of course).

Collapse
jarjanazy profile image
Abdulcelil Cercenazi Author

yes that is certainly a downside