Returning errors to a user is crucial during web application development. When users send an incorrect request that cannot be processed or want to get information on a non-existing object, your web application should let them know what is wrong.

There are different general HTTP status codes, for example, 400 for Bad Request or 404 for Not Found. Handling errors is very important, as it allows users to understand what is wrong right away.

Here you will find two ways to return an error message in Spring Boot applications. You can either use the ResponseStatusException Spring class or create your own exception using the @ResponseStatus annotation. Let’s write a simple code and show how it works.

Controller preparation

Imagine a web app that returns information about a flight by its number. It would look like this in JSON:

{
  "id" : 3,
  "from": "Berlin Tegel",
  "to": "Stuttgart",
  "gate": "D80"
}

In the example below, we create a simple FlightInfo class with information about the airport, city, and gate. We do not provide the flight date and time for the sake of brevity:

public class FlightInfo {

    private int id;
    private String from;
    private String to;
    private String gate;
 
    // constructor
 
    // getters and setters
 
}

Now we can implement a simple FlightController controller with a list of flights. We will also use a method that returns a FlightInfo object from the flightInfoList list to get information about the specific flight:

@RestController
public class FlightController {

    private final List<FlightInfo> flightInfoList = new ArrayList<>();

    public FlightController() {
        flightInfoList.add(
                new FlightInfo(1, "Delhi Indira Gandhi", "Stuttgart", "D80"));
        flightInfoList.add(
                new FlightInfo(2, "Tokyo Haneda", "Frankfurt", "110"));
        flightInfoList.add(
                new FlightInfo(3, "Berlin Schönefeld", "Tenerife", "15"));
        flightInfoList.add(
                new FlightInfo(4, "Kilimanjaro Arusha", "Boston", "15"));
    }

    @GetMapping("flights/{id}")
    public FlightInfo getFlightInfo(@PathVariable int id) {
        for (FlightInfo flightInfo : flightInfoList) {
            if (flightInfo.getId() == id) {
                return flightInfo;
            }
        }
        throw new RuntimeException();
    }

}

Finally, we are ready to focus on handling exceptions!

ResponseStatusException

The first way to return an error is to use the ResponseStatusException class introduced in Spring 5 for basic error handling as part of org.springframework.web.server package. It’s RuntimeException and that’s why we don’t need to add it to a method signature.

There are three constructors in Spring to generate ResponseStatusException:

ResponseStatusException(HttpStatus status)
ResponseStatusException(HttpStatus status, java.lang.String reason)
ResponseStatusException(
        HttpStatus status, 
        java.lang.String reason, 
        java.lang.Throwable cause
)

We have created an instance providing HttpStatus and, optionally, the reason and cause. The reason is a simple message that explains the exception. The cause is a nested exception.

So, what HttpStatus types are there? The most common are 200 OK404 NOT_FOUND400 BAD_REQUEST403 FORBIDDEN, and 500 INTERNAL_SERVER_ERROR.

Let’s change our getFlightInfo method and write a code that generates an instance of ResponseStatusException. Let’s say users are looking for some information about a flight from the Berlin Schönefeld airport, but the airport is closed for maintenance. In this situation, we should return ResponseStatusException with BAD_REQUEST status and reason message:

@GetMapping("flights/{id}")
public FlightInfo getFlightInfo(@PathVariable int id) {
    for (FlightInfo flightInfo : flightInfoList) {
        if (flightInfo.getId() == id) {
            if (Objects.equals(flightInfo.getFrom(), "Berlin Schönefeld")) {
                throw new ResponseStatusException(HttpStatus.BAD_REQUEST, 
                        "Berlin Schönefeld is closed for service today");
            } else {
                return flightInfo;
            }
        }
    }

    throw new RuntimeException();
}

If we try to test it, we will see the standard error info format as a response:

This JSON instance provides more information about the situation than a specified message — the timestamp, error name, status code, and the REST path of the request.

By default, Spring Boot doesn’t include the message field in a response. To enable it, add this line in the application.properties file: server.error.include-message=always

Let’s talk about the pros and cons of ResponseStatusException.

It has many benefits, allowing us to:

  • process exceptions of the same type separately;
  • set different status codes for the response;
  • avoid creating any additional exception classes;
  • throw an exception at any place;

The disadvantage is the code duplication since we have to write the same code in several controllers.

If your application throws an uncaught exception like RuntimeException or any other that doesn’t have explicit details on the HTTP code, it will be converted to 500 Internal Server Error. This status code indicates that something is bad with your server, and it should be fixed because the user requests cannot be processed properly.

Custom exceptions

It is also possible to set the response code and status for the custom exception. We can write a class that extends RuntimeException and add the @ResponseStatus annotation to the exception like this:

@ResponseStatus(code = HttpStatus.BAD_REQUEST)
class FlightNotFoundException extends RuntimeException {
    
    public FlightNotFoundException(String cause) {
        super(cause);
    }
}

Now, we can throw this exception in the same way as ResponseStatusException. The status will be set automatically.

For example, in the flight controller:

@GetMapping("flights/{id}")
public FlightInfo getFlightInfo(@PathVariable int id) {
    for (FlightInfo flightInfo : flightInfoList) {
        if (flightInfo.getId() == id) {
            return flightInfo;
        }
    }

    throw new FlightNotFoundException("Flight not found for id =" + id);
}

If we test this exception with a nonexistent id=1111, we get a response with the new status code 400.

The main advantage is that we can create our own specific exceptions and keep our code more readable.

On the other hand, custom exceptions require implementing additional classes.

Conclusion

Remember that bad exception processing may result in bugs and low readability. We have considered two ways of handling exceptions in Spring. Now you can:

  • throw ResponseStatusException
  • create custom exceptions using the @ResponseStatus annotation and throw them like ResponseStatusException

Each way has its advantages and disadvantages. Use the second option for specific exceptions or the first one to avoid additional exception classes.

Leave a Reply

Your email address will not be published.