Spring: Responding nicely with a strongly typed ResponseEntity and CompletionStage

When writing Spring / Spring boot REST webservices you often want to control your response mediatype, cache headers, and status codes, along side with implementing fully asynchronous endpoints.

Imagine we’re developing a service that returns car models fetched from a “slow” repository:

// Car repository used by our webservice
private CompletionStage<Car> createCar(
    final String carModel,
    final String carColor) {
    // Example only. Always validate input before using.
    return CompletableFuture.supplyAsync(() -> {
            final var car = new Car();
            car.setModel(carModel);
            car.setColor(carColor);
            car.setRandomVinNumber();
            // Obviously this takes some time...
            manufactureCar();
            return car;
        });
    }

private void manufactureCar() {
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

Using CompletableFuture and CompletionStage will help you to achieve asynchronous chaining in the implementation of your controller endpoints logic:

@PostMapping("/car/{carModel}/{carColor}")
public CompletionStage<Car> createCar(
    final @PathVariable String carModel,
    final @PathVariable String carColor)
    {
        return this.carRepository.createCar(carModel, carColor);
    }

However, specifying the HTTP status code, setting cache headers and so forth on a CompletionStage response is “intuitively” not possible. You will first need to wrap your response in a ResponseEntity:

public CompletionStage<ResponseEntity<Car>> createCar(
    final @PathVariable String carModel,
    final @PathVariable String carColor)
    {
        return this.carRepository.createCar(carModel, carColor)
                .thenApply(createdCar->
                ResponseEntity
                    .status(HttpStatus.OK)
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .cacheControl(CacheControl.noCache())
                    .cacheControl(CacheControl.noStore())
                    .cacheControl(CacheControl
                        .maxAge(-1, TimeUnit.DAYS))
                    .body(createdCar));
    }

I’ve found making shorthand functions, for repeated code-blocks like the above, to be very helpful. I suggest placing it in a core class of your project, and that you do it as a static or not, as you see fit:

public <T> ResponseEntity<T> noCache(T responseObject, HttpStatus statusCode) {
    return ResponseEntity
            .status(statusCode)
                ResponseEntity
                    .status(HttpStatus.OK)
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .cacheControl(CacheControl.noCache())
                    .cacheControl(CacheControl.noStore())
                    .cacheControl(CacheControl
                        .maxAge(-1, TimeUnit.DAYS))
                    .body(responseObject);
    }

Making the above controller endpoint logic look something like this:

public CompletionStage<ResponseEntity<Car>> createCar(
    final @PathVariable String carModel,
    final @PathVariable String carColor)
    {
        return this.carRepository.createCar(carModel, carColor)
                .thenApply(createdCar->
                        noCache(createdCar, HttpStatus.CREATED));
    }

Enjoy.

Leave a Reply

Your email address will not be published. Required fields are marked *