3 min read

Response codes for RESTful Services

November 13, 2020

In our previous installment, We have made sure our application follow hyper-media format called HAL. However, At most of the controller methods we have been throwing RuntimeException. These by design would cause 500 Error codes. But REST principles dictates that proper message or indication must be given back to client when possible.

GET http://localhost:8080/carts/4

HTTP/1.1 500 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 12 Nov 2020 23:01:24 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "timestamp": "2020-11-12T23:01:24.911+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "message": "",
  "path": "/carts/4"
}

For example if a resource was not available, then a 404 not found would be an appropriate response. Let’s try to achieve this behaviour. I will take the CartController for this example. Let’s try to respond back with a 404 if a cart with given id does not exist.

Here is attempt number 1 using the spring-mvc’s ResponseEntity wrapper.

    @GetMapping("/{cartId}")
    ResponseEntity<EntityModel<Cart>> get(@PathVariable Integer cartId) {
        Optional<Cart> byId = cartRepository.findById(cartId);
        if (!byId.isPresent()) {
            return ResponseEntity.notFound().build();
        }
        EntityModel<Cart> entityModel = EntityModel.of(byId.get(),
                linkTo(methodOn(CartController.class).get(cartId)).withSelfRel());
        return ResponseEntity.ok().body(entityModel);
    }

Even though this would work, There are couple of problems with this approach. Even before this change, the response code was already for records that exist. This is additional code debt. Also we cant keep checking the records for every possible scenario.

Here is an attempt with cleaner approach. First create a new Exception for scenarios where resources that doesn’t exist. Make sure this exception is extending RuntimeException. Annotate this Exception with @ResponseStatus.

package com.springhow.examples.springboot.rest.exceptions;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    
}

Here, This @ResponseStatus bind annotation is part of SpringMVC and it contains an already existing auto-configuration that will take care of the Response’s status code. Now throw this exception wherever you want to have 404 response code if the resource doesn’t exist. So the attempt 1 can be re-written as

     @GetMapping("/{cartId}")
     EntityModel<Cart> get(@PathVariable Integer cartId) {
         Cart cart = cartRepository.findById(cartId).orElseThrow(ResourceNotFoundException::new);
         return EntityModel.of(cart,
                 linkTo(methodOn(CartController.class).get(cartId)).withSelfRel());
     }

let’s try calling this API for an invalid Cart id.

GET http://localhost:8080/carts/4

HTTP/1.1 404 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 12 Nov 2020 23:05:33 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "timestamp": "2020-11-12T23:05:33.911+00:00",
  "status": 404,
  "error": "Not Found",
  "message": "",
  "path": "/carts/4"
}

Notice how the response HTTP code as well as the status error message has changed. In the same regard, Some of the responses may need positive varients of HTTP response codes. For example a DELETE method’s appropriate response code would be 204 No Content. And a create method would return 201 Created or 202 Accepted as a response. As these are success cases, there are no exceptions to use. But there is also an easier way to do this. Just annotate the method with @ResponseStatus as shown below.

    @PostMapping("/")
    @ResponseStatus(HttpStatus.CREATED)    EntityModel<Cart> create(@RequestBody Cart cart) {
        cart.setStatus(CartStatus.NEW);
        Cart created = cartRepository.save(cart);
        return EntityModel.of(created,
                linkTo(methodOn(CartController.class).get(created.getId())).withSelfRel());
    }

Summary

We are at the end of this series and I would like to recollect wha we have learned. In the beginning of this series we learned about Restful services and its basics. Once we got to know, We tried implementing a crude version of it. Once we knew that we were in the right track we made sure the service follows all RESTful principles by implementing hateoas and appropriate messages and error codes.

About the author
Raja Anbazhagan

I'm a Software Engineer with over 7 years of experience in working with Enterprise Java applications. Lately, I am focused on cloud-based Java applications and serverless technologies. I spend most of my spare time on stackoverflow and this blog.