Theory:REST Exception handling
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"
}
2
3
4
5
6
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
}
2
3
4
5
6
7
8
9
10
11
12
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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
)
2
3
4
5
6
7
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 OK
, 404 NOT_FOUND
, 400 BAD_REQUEST
, 403 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 static 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();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}
}
2
3
4
5
6
7
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);
}
2
3
4
5
6
7
8
9
10
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 likeResponseStatusException
Each way has its advantages and disadvantages. Use the second option for specific exceptions or the first one to avoid additional exception classes.