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.