Response Types
- Challenges with
ResponseEntityin Reactive Applications - hateoflux Response Types
- Common Features
- Choosing the Right Response Type
- Auto-configuration
Challenges with ResponseEntity in Reactive Applications
In Spring WebFlux, handling reactive responses with ResponseEntity introduces specific challenges, especially when working with Mono and Flux. These challenges center around when and how to finalize HTTP status codes, headers, and body content.
Single-Item Responses with Mono
For single-item responses, Mono<ResponseEntity<T>> allows delaying the creation of the response until the data is available. This enables dynamically setting the HTTP status code and headers based on the body. For instance, you can return a 404 Not Found if the body is empty or 200 OK if data is present:
Mono<ResponseEntity<T>> response = someService.getData()
.map(data -> ResponseEntity.ok(data))
.defaultIfEmpty(ResponseEntity.notFound().build());
This flexibility ensures that decisions about the response can be made reactively, just before the response is sent to the client.
Streaming Responses with Flux
Streaming responses using ResponseEntity<Flux<T>> require the HTTP status and headers to be finalized before emitting any data. Once the first element of the Flux is sent, the response is committed, and the status or headers cannot be modified. This is a fundamental limitation of streaming in HTTP. For example:
ResponseEntity<Flux<T>> response = ResponseEntity.ok(someService.getStreamingData());
Here, the 200 OK status is decided upfront and sent immediately, regardless of the subsequent content of the stream. This means it is not possible to base the HTTP status on the content of the stream after it starts.
hateoflux Response Types
hateoflux provides a unified API to handle the complexities of constructing ResponseEntity objects in reactive Spring WebFlux applications. It manages the underlying response fields and the timing of ResponseEntity creation, allowing developers to build single-resource, list-based, or streaming responses consistently without manually handling the intricacies of Mono and Flux wrappers.
HalResourceResponse Response Type
Used when returning a single HAL resource. This response type wraps a single HalResourceWrapper and converts it into a proper reactive response.
@GetMapping("/{id}")
public HalResourceResponse<Product, Void> getProduct(@PathVariable String id, ServerWebExchange exchange) {
Mono<HalResourceWrapper<Product, Void>> product =
productAssembler.wrapInResourceWrapper(productService.findById(id), exchange);
return HalResourceResponse.ok(product);
}
Under the hood, HalResourceResponse converts to:
Mono<ResponseEntity<HalResourceWrapper<ResourceT, EmbeddedT>>>
HalMultiResourceResponse Response Type
Designed for streaming multiple individual HAL resources. This is particularly useful when you want to stream resources one by one rather than collecting them into a list.
@GetMapping("/")
public HalMultiResourceResponse<Product, Void> streamProducts(ServerWebExchange exchange) {
Flux<HalResourceWrapper<Product, Void>> products =
productService.findAll()
.flatMap(product -> productAssembler.wrapInResourceWrapper(product, exchange));
return HalMultiResourceResponse.ok(products);
}
Under the hood, HalMultiResourceResponse converts to:
Mono<ResponseEntity<Flux<HalResourceWrapper<ResourceT, EmbeddedT>>>>
Note that HalMultiResourceResponse is the only one that puts the wrapper in a Flux (or a publisher in general), whereas the others embed the wrappers directly in the ResponseEntity.
HalListResponse Response Type
Used when returning a collection of resources as a single HAL document, particularly useful when including pagination metadata.
@GetMapping
public HalListResponse<Product, Void> getProducts(Pageable pageable, ServerWebExchange exchange) {
Mono<HalListWrapper<Product, Void>> products =
productAssembler.wrapInListWrapper(productService.findAll(pageable), exchange);
return HalListResponse.ok(products);
}
Under the hood, HalListResponse converts to:
Mono<ResponseEntity<HalListWrapper<ResourceT, EmbeddedT>>>
Common Features
Fluent Http Header Builder
All response types extend HttpHeadersModule, providing a fluent API for header manipulation:
return HalResourceResponse.ok(product)
.withContentType(MediaType.APPLICATION_JSON)
.withETag("\"123456\"")
.withHeader("Custom-Header", "value");
Status Codes
In addition to factory methods that enable the creation of a response with arbitrary status codes:
return HalResourceResponse.of(HttpStatus.I_AM_A_TEAPOT);
// or
return HalResourceResponse.of(someObject, HttpStatus.PARTIAL_CONTENT);
Each response type includes also factory methods for common HTTP status codes:
ok()-HTTP 200created()-HTTP 201accepted()-HTTP 202noContent()-HTTP 204notFound()-HTTP 404
Status codes in Hal*Responses are not designed to include detailed messages, especially error messages. Hal*Responses allow for flexibility in API design by enabling expressiveness without relying on exceptions. For example, HTTP 206 Partial Content and 304 Not Modified are used to convey specific states that do not necessarily represent exceptions. However, in cases where an exception does occur, it is advisable to use an ExceptionHandler instead.
Type Safety and Reduced Boilerplate
hateoflux’s custom response types not only simplify controller method signatures but also maintain type safety through generics. Instead of explicitly wrapping responses in Mono and ResponseEntity, the library encapsulates these structures, reducing boilerplate while ensuring type correctness.
For example, instead of writing:
Mono<ResponseEntity<HalListWrapper<ResourceT, EmbeddedT>>> methodName(...) { ... }
You can use a concise and type-safe response type:
HalListResponse<ResourceT, EmbeddedT> methodName(...) { ... }
Choosing the Right Response Type
HalResourceResponse: Use when your API endpoint returns a singleHalResourceWrapper.HalMultiResourceResponse: Use when your endpoint returns multipleHalResourceWrapper.HalListResponse: Use when your endpoint returns a singleHalListWrapper.
Auto-configuration
hateoflux automatically configures the necessary components for response handling through ReactiveResponseEntityConfig. No additional setup is required beyond having the dependency on your classpath.