Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this short article, we'll see how exceptions should be thrown in our controllers and how to test these exceptions using Spring MockMvc.

2. Throwing Exceptions in Controllers

Let's start learning how to launch an exception from a controller.

We can think of the services we expose from a controller in the same way as if they were normal Java functions:

@GetMapping("/exception/throw")
public void getException() throws Exception {
    throw new Exception("error");
}

Now, let's see what happens when we call this service. First, we'll notice that the response code of the service is 500 which means Internal Server Error.

Secondly, we receive a response body like this:

{
    "timestamp": 1592074599854,
    "status": 500,
    "error": "Internal Server Error",
    "message": "No message available",
    "trace": "java.lang.Exception
              at com.baeldung.controllers.ExceptionController.getException(ExceptionController.java:26)
              ..."
}

In conclusion, when we throw an exception from a RestController, the service response is automatically mapped to a 500 response code, and the stack trace of the exception is included in the response body.

3. Mapping Exceptions to HTTP Response Codes

Now we're going to learn how to map our exceptions to different response codes other than 500.

To achieve this, we're going to create custom exceptions and use the ResponseStatus annotation that's provided by Spring. Let's create those custom exceptions:

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadArgumentsException extends RuntimeException {

    public BadArgumentsException(String message) {
        super(message);
    }
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalException extends RuntimeException {

    public InternalException(String message) {
        super(message);
    }
}
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {

    public ResourceNotFoundException(String message) {
        super(message);
    }
}

The second and last step is to create a simple service in our controller to throw these exceptions:

@GetMapping("/exception/{exception_id}")
public void getSpecificException(@PathVariable("exception_id") String pException) {
    if("not_found".equals(pException)) {
        throw new ResourceNotFoundException("resource not found");
    }
    else if("bad_arguments".equals(pException)) {
        throw new BadArgumentsException("bad arguments");
    }
    else {
        throw new InternalException("internal error");
    }
}

Now, let's see the different responses of the service for the different exceptions that we mapped:

  • For not_found, we receive a response code of 404
  • Given the value bad_arguments, we receive a response code of 400
  • For any other value, we still receive 500 as the response code

Apart from the response codes, we'll receive a body with the same format as the response body received in the previous section.

4. Testing Our Controllers

Finally, we're going to see how to test that our controller is throwing the correct exceptions.

The first step is to create a test class and create an instance of MockMvc:

@Autowired
private MockMvc mvc;

Next, let's create the test cases for each of the values that our service can receive:

@Test
public void givenNotFound_whenGetSpecificException_thenNotFoundCode() throws Exception {
    String exceptionParam = "not_found";

    mvc.perform(get("/exception/{exception_id}", exceptionParam)
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isNotFound())
      .andExpect(result -> assertTrue(result.getResolvedException() instanceof ResourceNotFoundException))
      .andExpect(result -> assertEquals("resource not found", result.getResolvedException().getMessage()));
}

@Test
public void givenBadArguments_whenGetSpecificException_thenBadRequest() throws Exception {
    String exceptionParam = "bad_arguments";

    mvc.perform(get("/exception/{exception_id}", exceptionParam)
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isBadRequest())
      .andExpect(result -> assertTrue(result.getResolvedException() instanceof BadArgumentsException))
      .andExpect(result -> assertEquals("bad arguments", result.getResolvedException().getMessage()));
}

@Test
public void givenOther_whenGetSpecificException_thenInternalServerError() throws Exception {
    String exceptionParam = "dummy";

    mvc.perform(get("/exception/{exception_id}", exceptionParam)
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isInternalServerError())
      .andExpect(result -> assertTrue(result.getResolvedException() instanceof InternalException))
      .andExpect(result -> assertEquals("internal error", result.getResolvedException().getMessage()));
}

With these tests, we're checking that the response code, the type of exception raised, and the messages of that exceptions are the expected ones for each of the values.

5. Conclusion

In this tutorial, we have learned how to handle exceptions in our Spring RestControllers and how to test that each exposed service is throwing the expected exceptions.

As always, the full source code of the article is available in GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
guest
0 Comments
Inline Feedbacks
View all comments