In this tutorial, we will how to customize Exceptions in Spring Boot REST Applications to suit our needs.

Exception handling is a key element of developing application and services. Spring Boot provides good default exception handling mechanism.

To understand how the default exception handling provided by Spring Boot works, let's start requesting to a nonexistent URL.

curl -s http://localhost:8080/unknown | jq

{

"timestamp": "2020-04-01T13:12:00.327+0000",

"status": 404,

"error": "Not Found",

"message": "No message available",

"path": "/unknown"

}

Key things to note are as follows:

  • The response header has an HTTP status of 404 - Resource Not Found
  • Spring Boot returns a valid JSON message as a response with the message stating that the resource is not found

Throwing Exceptions from the Endpoint

Let's create a resource that throws an exception, and send a GET request to it in order to understand how the application reacts to runtime exceptions.

Check the following code snippet:

@RequestMapping("/list")
public List<Customer> findAll()
{

    throw new RuntimeException("Some Exception Occured");
    
}

Let's fire a GET request to the preceding service at http://localhost:8080/list/

The response is as shown in the following code:

 curl -s http://localhost:8080/list | jq
{
  "timestamp": "2020-04-01T13:15:11.091+0000",
  "status": 500,
  "error": "Internal Server Error",
  "message": "Some Exception Occured",
  "path": "/list"
}

Some important things to note are as follows:

  • The response header has an HTTP status of 500 ; Internal server error
  • Spring Boot also returns the message which contains the error thown.

Creating a Custom Exception

Let's see a more complex example which uses a custom Exception class

@RequestMapping("/find/{id}")
public Customer findOne(@PathVariable int id)
{
    Customer c =  repository.findCustomer(id);

    if (c != null)
        return c;
    else
        throw new CustomerNotFoundException(id);
    
}

As you can see, in this case we are throwing a CustomerNotFoundException if your search returns no data.

In this case, if we return a 500 Internal Server Error it would not be so meaningful for the Client, which cannot guess how to fix the error. On the other hand, if we return an error with status code 404 it would be more appropriate.

We can do it using a ControllerAdvice with @ExceptionHandler for custom exception, it will handle the custom exception, populate and return the custom response as below.

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {


    @ExceptionHandler(CustomerNotFoundException.class)
    public void springHandleNotFound(HttpServletResponse response) throws IOException {
        response.sendError(HttpStatus.NOT_FOUND.value());
    }


}

And here is the CustomerNotFoundException class:

package com.example.testrest;


public class CustomerNotFoundException extends RuntimeException {

    public CustomerNotFoundException(int id) {
        super("Customer id not found : " + id);
    }

}

So, right now if you request a customer which is not found, you will get as Response a JSON with the CustomerNotFoundException's error message and a status of 404, instead of a generic 500 error:

 $ curl -s http://localhost:8080/find/9 | jq
{
  "timestamp": "2020-04-01T14:01:47.637+0000",
  "status": 404,
  "error": "Not Found",
  "message": "Customer id not found : 9",
  "path": "/one/9"
}

How to customize all Exception Responses

To override the default JSON error response for all exceptions, simply create a Class which extends DefaultErrorAttributes. Let's see how we could add in the list of error attribures the application version:

import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    private static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {

   
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);

         
        Object timestamp = errorAttributes.get("timestamp");
        if (timestamp == null) {
            errorAttributes.put("timestamp", dateFormat.format(new Date()));
        } else {
            errorAttributes.put("timestamp", dateFormat.format((Date) timestamp));
        }

        // Custom Attribute
        errorAttributes.put("app.version", "2.2");

        return errorAttributes;

    }

}

In this case, the returned JSON will be:

curl -s http://localhost:8080/query/9 | jq
{
  "timestamp": "2020/04/01 16:25:29",
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/query/9",
  "app.version": "2.2"
}

Source code for this tutorial: https://github.com/fmarchioni/masterspringboot/tree/master/exception

FREE WildFly Application Server - JBoss - Quarkus - Drools Tutorials