I had a similar issue. To solve this issue, you need to first convert your cold flux into a hot flux. Then on the hot flux call .next()
, to return a Mono<Starship>
. On this mono, call .flatMap().switchIfEmpty().onErrorResume()
. In the flatMap()
concat the returned startship object with the hot flux.
Here's the code snippet modified to achieve what you want:
public Mono<ServerResponse> getStarships(ServerRequest request)
{
String starshipType = request.pathVariable("type");
Flux<Starship> coldStarshipFlux = starshipService.getFromSpacedock(starshipType);
//The following step is a very important step. It converts your cold flux into a hot flux.
Flux<Startship> hotStarshipFlux = coldStarshipFlux
.publish()
.refCount(1, Duration.ofSeconds(2));
return hotStarshipFlux.next()
.flatMap( starShipObj ->
{
Flux<Starship> flux = Mono.just(starShipObj)
.concatWith(hotStarshipFlux);
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(flux, Starship.class);
}
)
.switchIfEmpty(
ServerResponse.notFound().build()
)
.onErrorResume( t ->
{
if(t instanceof InvalidStarshipTypeException)
{
return ServerResponse.badRequest()
.contentType(MediaType.TEXT_PLAIN)
.bodyValue(t.getMessage());
}
else
{
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.TEXT_PLAIN)
.bodyValue(t.getMessage());
}
});
}
The code .publish().refCount(1, Duration.ofSeconds(2));
is what makes your cold flux into a hot flux. It is important that you do this.
When using a hot flux, each new subscription shares the elements emitted in the flux stream, which are emitted after the subscription began. So, the initial call to .next()
causes the hot flux to emit the first element. Now, when the .concatWith()
method is called again on the same hot flux, the hot flux will not re-emit the first element again, but will continue emitting the subsequent elements in its stream. So all remaining elements in the stream, starting with the second will get concatenated.
If you did not convert your flux into a hot flux, but ran the above code on your cold flux, then the calls to .next()
and .concatWith()
would each cause your cold flux to re-generate the same data anew twice, once for .next()
, and once for .concatWith()
. Thus, you'd have a duplicate first element.
Now, you may ask, why bother with all this hot and cold flux? Why not just do something like the following code snippet using just a cold flux? After all a cold will will regenerate the data anew so there won't be a need to call the concatWith()
method at all.
Flux<Starship> coldStarshipFlux = starshipService.getFromSpacedock(starshipType);
return coldStarldshipFlux.next()
.flatMap( starShipObj -> // ignore the startShipObj
{
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(coldStarldshipFlux, Starship.class);
}
)
.switchIfEmpty(
... //same code as above hot flux
)
.onErrorResume( t ->
{
... //same code as as above hot flux
});
The problem with the above code snippet is that all subscriptions on a cold flux re-generate data anew, per subscription. So, effectively the call to .next()
and .concatWith()
would cause the cold flux to re-generate the same data stream anew, and depending on how your startshipService is coded, may result in a second HTTP request being made. Thus, effectively, you'd be making two HTTP requests instead of one.
When using a hot flux, the need to re-generate data anew (thereby potentially making a second HTTP request) is avoided. That's the biggest advantage of converting your cold flux into a hot flux.
For details on hot and cold flux see the following web site. Towards the end, it very clearly explains the difference between a hot and cold flux, and how they behave differently:
https://spring.io/blog/2019/03/06/flight-of-the-flux-1-assembly-vs-subscription
I hope this answer helps you.