Handle Spring Exceptions Like A Pro
Tags: webdev, java, tutorial, programming
Published: 2021-08-04 19:42:00


## 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...

```java
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:
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 🙌🏽

```java 
@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.

```java
@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
```java
@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 🧠
```java
@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](https://www.baeldung.com/exception-handling-for-rest-with-spring)👈
---
### Check the code on [GitHub](https://github.com/Jarjanazy/spring-global-exception-handling)🥷