1. Overview
In this quick tutorial, weβll discuss the new ResponseStatusException class introduced in Spring 5. This class supports the application of HTTP status codes to HTTP responses.
A RESTful application can communicate the success or failure of an HTTP request by returning the right status code in the response to the client. Simply put, an appropriate status code can help the client to identify problems that might have occurred while the application was dealing with the request.
2. ResponseStatus
Before we delve into ResponseStatusException, letβs quickly take a look at the @ResponseStatus annotation. This annotation was introduced in Spring 3 for applying HTTP Status code to an HTTP response.
We can use the @ResponseStatus annotation to set the status and reason in our HTTP response:
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Actor Not Found")
public class ActorNotFoundException extends Exception {
// ...
}
If this exception is thrown while processing an HTTP request, then the response will include the HTTP status specified in this annotation.
One drawback of the @ResponseStatus approach is that it creates tight coupling with the exception. In our example, all exceptions of type ActorNotFoundException will generate the same error message and status code in the response.
3. ResponseStatusException
ResponseStatusException is a programmatic alternative to @ResponseStatus and is the base class for exceptions used for applying a status code to an HTTP response. Itβs a RuntimeException and hence not required to be explicitly added in a method signature.
Spring provides 3 constructors to generate ResponseStatusException:
ResponseStatusException(HttpStatus status)
ResponseStatusException(HttpStatus status, java.lang.String reason)
ResponseStatusException(
HttpStatus status,
java.lang.String reason,
java.lang.Throwable cause
)
ResponseStatusException, constructor arguments:
- status β an HTTP status set to the HTTP response
- reason β a message explaining the exception set to the HTTP response
- cause β a Throwable cause of the ResponseStatusException
Note: in Spring, HandlerExceptionResolver intercepts and processes any exception raised and not handled by a Controller.
One of these handlers, ResponseStatusExceptionResolver, looks for any ResponseStatusException or uncaught exceptions annotated by @ResponseStatus and then extracts the HTTP Status code & reason and includes them in the HTTP response.
3.1. ResponseStatusException Benefits
ResponseStatusException usage has few benefits:
- Firstly, exceptions of the same type can be processed separately and different status codes can be set on the response, reducing tight coupling
- Secondly, it avoids the creation of unnecessary additional exception classes
- Finally, it provides more control over exception handling, as the exceptions can be created programmatically
4. Examples
4.1. Generate ResponseStatusException
Now, letβs see an example that generates a ResponseStatusException :
@GetMapping("/actor/{id}")
public String getActorName(@PathVariable("id") int id) {
try {
return actorService.getActor(id);
} catch (ActorNotFoundException ex) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Actor Not Found", ex);
}
}
Spring Boot provides a default /error mapping, returning a JSON response with HTTP status.
Hereβs how the response looks:
$ curl -i -s -X GET 'http://localhost:8081/actor/8'
HTTP/1.1 404
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 26 Dec 2020 19:38:09 GMT
{
"timestamp": "2020-12-26T19:38:09.426+00:00",
"status": 404,
"error": "Not Found",
"path": "/actor/8"
}
Sharp eyes may have noticed, the response doesnβt have the βmessageβ field to show the reason text βActor Not Foundβ.
Next, letβs figure out why this happened and how to include the βmessageβ field in the response.
4.2. A Few Words About the server.error.include-message Property
The server.error.include-message property, as its name implies, controls whether the βmessageβ field is included in the error response or not. It supports three different values:
- always β Error responses always include the βmessageβ field.
- never β The βmessageβ field never appears in error responses.
- on_param β The βmessageβ field will come into error responses only if the request has the parameter βmessage=trueβ. Otherwise, the response wonβt include βmessageβ.
Starting from the 2.3 version, Spring Boot uses never as the default value for the property. In other words, it doesnβt include an error message on the default error page. The reason is to reduce the risk of leaking information to a client.
Therefore, the βmessageβ information didnβt come into the error response of http://localhost:8081/actor/8.
Next, letβs set server.error.include-message=on_param and see what happens:
$ curl -i -s -X GET 'http://localhost:8081/actor/8'
HTTP/1.1 404
...
{
"timestamp": "2020-12-26T19:38:49.426+00:00",
"status": 404,
"error": "Not Found",
"path": "/actor/8"
}
As we can see, if the request is sent without the βmessage=trueβ parameter, the response doesnβt include βmessageβ. If we add that parameter to the request, weβll see the βmessageβ field:
$ curl -i -s -X GET 'http://localhost:8081/actor/8?message=true'
HTTP/1.1 404
...
{
"timestamp": "2020-12-26T19:49:11.426+00:00",
"status": 404,
"error": "Not Found",
"message": "Actor Not Found",
"path": "/actor/8"
}
Itβs worth mentioning that Spring Boot DevTools takes always as the default value for the server.error.include-message property. Therefore, if we donβt specify this propertyβs value and spring-boot-devtools is in the classpath, the application will always show βmessageβ in the response. This is helpful for us to test and debug the application during development.
For simplicity, weβll set server.error.include-message=always in this tutorial, to see complete error messages.
4.3. Different Status Code β Same Exception Type
Now, letβs see how a different status code is set to HTTP response when the same type of exception is raised:
@PutMapping("/actor/{id}/{name}")
public String updateActorName(
@PathVariable("id") int id,
@PathVariable("name") String name) {
try {
return actorService.updateActor(id, name);
} catch (ActorNotFoundException ex) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "Provide correct Actor Id", ex);
}
}
Hereβs how the response looks:
$ curl -i -s -X PUT 'http://localhost:8081/actor/8/BradPitt'
HTTP/1.1 400
...
{
"timestamp": "2018-02-01T04:28:32.917+0000",
"status": 400,
"error": "Bad Request",
"message": "Provide correct Actor Id",
"path": "/actor/8/BradPitt"
}
5. Conclusion
In this quick tutorial, we discussed how to construct a ResponseStatusException in our program.
We also emphasized how itβs programmatically a better way to set HTTP status codes in HTTP Response than @ResponseStatus annotation.
