How to Return HTTP Status Codes in a Spring Boot Application - Stack Abuse

How to Return HTTP Status Codes in a Spring Boot Application

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:

  1. Informational (1xx): Indicates that the request was received and the process is continuing. It alerts the sender to wait for a final response.
  2. Successful (2xx): Indicates that the request was successfully received, understood, and accepted.
  3. Redirection (3xx): Indicates that further action must be taken to complete the request.
  4. Client Errors (4xx): Indicates that an error occurred during the request processing and it is the client who caused the error.
  5. 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.

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)

@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:

Free eBook: Git Essentials

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!

    @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.

Last Updated: June 29th, 2021
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Want a remote job?

    Prepping for an interview?

    • Improve your skills by solving one coding problem every day
    • Get the solutions the next morning via email
    • Practice on actual problems asked by top companies, like:
     
     
     

    Better understand your data with visualizations

    •  30-day no-questions refunds
    •  Beginner to Advanced
    •  Updated regularly (update June 2021)
    •  New bonus resources and guides

    © 2013-2021 Stack Abuse. All rights reserved.