Introduction
All Software Engineers that rely on external/third-party services or tools over HTTP would like to know whether their requests have been accepted, and if not - what's going on.
Your role as an API developer is to provide a good experience for your users, and amongst other things - satisfy this demand. Making it easy for other developers to determine whether your API returns an error or not gets you a long way, and in the former case - letting other developers know why gets you even farther.
Is the error caused by an internal service of the API? Did they send an unparseable value? Did the server processing these requests outright crash?
Narrowing the possibilities of failure allows developers using your service do their job more efficiently. This is where HTTP status codes come into play, with a short message in the response's body, describing what's going on.
In this guide, we'll take a look at how to return different HTTP Status Codes in Spring Boot, while developing a REST API.
What are HTTP status codes?
Simply put, an HTTP Status Code refers to a 3-digit code that is part of a server's HTTP Response. The first digit of the code describes the category in which the response falls. This already gives a hint to determine whether the request was successful or not. The Internet Assigned Numbers Authority (IANA) maintains the official registry of HTTP Status Codes. Below are the different categories:
- Informational (1xx): Indicates that the request was received and the process is continuing. It alerts the sender to wait for a final response.
- Successful (2xx): Indicates that the request was successfully received, understood, and accepted.
- Redirection (3xx): Indicates that further action must be taken to complete the request.
- Client Errors (4xx): Indicates that an error occurred during the request processing and it is the client who caused the error.
- Server Errors (5xx): Indicates that an error occurred during request processing but that it was by the server.
While the list is hardly exhaustive, here are some of the most common HTTP codes you'll be running into:
Code | Status | Description |
200 | OK | The request was successfully completed. |
201 | Created | A new resource was successfully created. |
400 | Bad Request | The request was invalid. |
401 | Unauthorized | The request did not include an authentication token or the authentication token was expired. |
403 | Forbidden | The client did not have permission to access the requested resource. |
404 | Not Found | The requested resource was not found. |
405 | Method Not Allowed | The HTTP method in the request was not supported by the resource. For example, the DELETE method cannot be used with the Agent API. |
500 | Internal Server Error | The request was not completed due to an internal error on the server side. |
503 | Service Unavailable | The server was unavailable. |
For a full list of HTTP status codes, see Wikipedia's List of HTTP status codes.
Return HTTP Status Codes in Spring Boot
Spring Boot makes the development of Spring-based applications so much easier than ever before, and it automatically returns appropriate status codes. If the request went through just fine, a 200 OK
is returned, while a 404 Not Found
is returned if the resource isn't found on the server.
Nevertheless, there are many situations in which we would like to decide on the HTTP Status Code that will be returned in the response ourselves and Spring Boot provides multiple ways for us to achieve that.
Let's start up a skeleton project via Spring Initializr:
Or via the Spring CLI:
$ spring init -d=web
We'll have a simple controller, TestController
:
@Controller
public class TestController {}
Here, we'll create a few request handlers that return different status codes, through a few different approaches.
Returning Response Status Codes with @ResponseStatus
This annotation takes as an argument, the HTTP Status Code, to be returned in the response. Spring makes our job easier by providing an enum containing all the HTTP status codes. It is a very versatile annotation and can be used in controllers at a class or method-level, on Custom Exception Classes, and on classes annotated with @ControllerAdvice
(at class or method level).
It works the same way in both classes annotated with @ControllerAdvice
and those annotated with @Controller
. It is usually coupled with the @ResponseBody
annotation in both cases. When used at the class-level, all class methods will result in a response with the specified HTTP status code. All method-level @ResponseStatus
annotations override the class-level code and if no @ResponseStatus
is associated with a method that doesn't throw an exception - a 200
is returned by default:
@Controller
@ResponseBody
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
public class TestController {
@GetMapping("/classlevel")
public String serviceUnavailable() {
return "The HTTP Status will be SERVICE_UNAVAILABLE (CODE 503)\n";
}
@GetMapping("/methodlevel")
@ResponseStatus(code = HttpStatus.OK, reason = "OK")
public String ok() {
return "Class Level HTTP Status Overriden. The HTTP Status will be OK (CODE 200)\n";
}
}
The class-level @ResponseStatus
becomes the default code to be returned for all methods, unless a method overrides it. The /classlevel
request handler isn't associated with a method-level status, so the class-level status kicks in, returning a 503 Service Unavailable
if someone hits the endpoint. On the other hand, the /methodlevel
endpoint returns an 200 OK
:
$ curl -i 'http://localhost:8080/classlevel'
HTTP/1.1 503
Content-Type: text/plain;charset=UTF-8
Content-Length: 55
Date: Thu, 17 Jun 2021 06:37:37 GMT
Connection: close
The HTTP Status will be SERVICE_UNAVAILABLE (CODE 503)
$ curl -i 'http://localhost:8080/methodlevel'
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 73
Date: Thu, 17 Jun 2021 06:41:08 GMT
Class Level HTTP Status Overriden. The HTTP Status will be OK (CODE 200)
When dealing with HTTP status codes, especially common ones like 404 Not Found, it's important to handle these effectively to improve user experience. Knowing how to fix 404 errors can greatly enhance your API's robustness and user satisfaction.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
@ResponseStatus
works differently when used on Custom Exception classes. Here, the HTTP Status code specified will be the one returned in the response when an exception of that type is thrown but is not caught. We will have a closer look at all this in the code in a later section.
Additionally, you can specify a reason
, which automatically triggers the HttpServletResponse.sendError()
method, which means that whatever you return won't come to pass:
@GetMapping("/methodlevel")
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Resource was not found on the server")
public String notFound() {
return "";
}
Though, to actually get the reason
to be sent via the sendError()
method, you'll have to set the include-message
property within application.properties
:
server.error.include-message=always
Now, if we send a request to /methodlevel
:
$ curl -i http://localhost:8080/methodlevel
HTTP/1.1 404
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 29 Jun 2021 16:52:28 GMT
{"timestamp":"2021-06-29T16:52:28.894+00:00","status":404,"error":"Not Found","message":"Resource was not found on the server","path":"/methodlevel"}
This is probably the simplest way to return an HTTP status, but also a rigid one. We can't really alter the status codes manually, through code here. This is where the ResponseEntity
class kicks in.
Returning Response Status Codes with ResponseEntity
The ResponseEntity
class is used when we one to programmatically specify all aspects of an HTTP response. This includes the headers, body, and, of course, the status code. This way is the most verbose way of returning an HTTP response in Spring Boot but also the most customizable. Many prefer to use the @ResponseBody
annotation coupled with @ResponseStatus
as they're simpler. A ResponseEntity
object can be created using one of the several constructors or via the static builder method:
@Controller
@ResponseBody
public class TestController {
@GetMapping("/response_entity")
public ResponseEntity<String> withResponseEntity() {
return ResponseEntity.status(HttpStatus.CREATED).body("HTTP Status will be CREATED (CODE 201)\n");
}
}
The main advantage of using a ResponseEntity
is that you can tie it in with other logic, such as:
@Controller
@ResponseBody
public class TestController {
@GetMapping("/response_entity")
public ResponseEntity<String> withResponseEntity() {
int randomInt = new Random().ints(1, 1, 11).findFirst().getAsInt();
if (randomInt < 9) {
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body("Expectation Failed from Client (CODE 417)\n");
} else {
return ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT).body("April Fool's Status Code (CODE 418)\n");
}
}
}
Here, we've generated a random integer within a range of 1 and 10, and returned a status code depending on the random integer. By checking if the randomInt
is greater than 9
, we've given the client a 10% probability of seeing the "I am a teapot" April Fool's status code, added to the RFC2324.
Sending several requests to this endpoint will eventually return:
$ curl -i 'http://localhost:8080/response_entity'
HTTP/1.1 418
Content-Type: text/plain;charset=UTF-8
Content-Length: 36
Date: Tue, 29 Jun 2021 16:36:21 GMT
April Fool's Status Code (CODE 418)
Returning Response Status Codes with ResponseStatusException
A class used to return status codes in exceptional cases is the ResponseStatusException
class. It is used to return a specific message and the HTTP status code that will be returned when an error occurs. It is an alternative to using @ExceptionHandler
and @ControllerAdvice
. Exception handling using ResponseStatusException
is considered to be more fine-grained. It avoids the creation of unnecessary additional Exception classes and reduces tight coupling between the status codes and the exception classes themselves:
@Controller
@ResponseBody
public class TestController {
@GetMapping("/rse")
public String withResponseStatusException() {
try {
throw new RuntimeException("Error Occurred");
} catch (RuntimeException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "HTTP Status will be NOT FOUND (CODE 404)\n");
}
}
}
It behaves much like when we set the reason
via a @ResponseStatus
since the underlying mechanism is the same - the sendError()
method:
$ curl -i http://localhost:8080/rse
HTTP/1.1 404
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 29 Jun 2021 17:00:23 GMT
{"timestamp":"2021-06-29T17:01:17.874+00:00","status":404,"error":"Not Found","message":"HTTP Status will be NOT FOUND (CODE 404)\n","path":"/rse"}
Custom Exception Classes and Returning HTTP Status Codes
Finally, another way to handle exceptions is via the @ResponseStatus
and @ControllerAdvice
annotations and custom exception classes. Although ResponseStatusException
is preferred, if for whatever reason it isn't in the picture, you can always use these.
Let's add two request handlers that throw new custom exceptions:
@Controller
@ResponseBody
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
public class TestController {
@GetMapping("/caught")
public String caughtException() {
throw new CaughtCustomException("Caught Exception Thrown\n");
}
@GetMapping("/uncaught")
public String unCaughtException() {
throw new UnCaughtException("The HTTP Status will be BAD REQUEST (CODE 400)\n");
}
}
Now, let's define these exceptions and their own default @ResponseStatus
codes (which override the class-level status):
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class CaughtCustomException extends RuntimeException{
public CaughtCustomException(String message) {
super(message);
}
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class UnCaughtException extends RuntimeException {
public UnCaughtException(String message) {
super(message);
}
}
Finally, we'll create a @ControllerAdvice
controller, which is used to set up how Spring Boot manages exceptions:
@ControllerAdvice
@ResponseBody
public class TestControllerAdvice {
@ExceptionHandler(CaughtCustomException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleException(CaughtCustomException exception) {
return String.format("The HTTP Status will be Internal Server Error (CODE 500)\n %s\n",exception.getMessage()) ;
}
}
Finally, when we fire up a few HTTP requests, the endpoint that returns the CaughCustomException
will be formatted according to the @ControllerAdvice
, while the UnCaughtCustomException
won't:
$ curl -i http://localhost:8080/caught
HTTP/1.1 500
Content-Type: text/plain;charset=UTF-8
Content-Length: 83
Date: Tue, 29 Jun 2021 17:10:01 GMT
Connection: close
The HTTP Status will be Internal Server Error (CODE 500)
Caught Exception Thrown
$ curl -i http://localhost:8080/uncaught
HTTP/1.1 400
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 29 Jun 2021 17:10:06 GMT
Connection: close
{"timestamp":"2021-06-29T17:10:06.264+00:00","status":400,"error":"Bad Request","message":"The HTTP Status will be BAD REQUEST (CODE 400)\n","path":"/uncaught"}
Conclusion
In this guide, we've taken a look at how to return HTTP Status Codes in Spring Boot using @ResponseStatus
, ResponseEntity
and ResponseStatusException
, as well as how to define custom exceptions and handle them both via @ControllerAdvice
and without it.