0

When using WebClient's exchangeToMono() the body retrieving part is always returning an empty Mono:

Example, the exposed service which returns a non-empty Mono

@PostMapping("/test")
public Mono<Pojo> getCalled(@RequestBody Pojo pojo) {
    System.out.println(pojo); // always prints a non-null object
    return Mono.just(pojo);
}

WebClient with .retrieve()

WebClient.create().post().uri(theUrl).bodyValue(p).retrieve().toEntity(Pojo.class).map(response -> {
    if (response.getStatusCode().isError()) {
        // do something;
    }
    return response.getBody();
}).single(); // always get a single element and does not fail

WebClient with .exchangeToMono()

WebClient.create().post().uri(theUrl).bodyValue(p).exchangeToMono(Mono::just).flatMap(response -> {
    if (response.statusCode().isError()) {
        // do something;
    }

    return response.bodyToMono(Pojo.class).single(); // fails with java.util.NoSuchElementException: Source was empty
});

Am I doing something wrong?

Fradantim
  • 31
  • 1
  • 6
  • why are you doing `.exchangeToMono(Mono::just)` and then flatMapping straight after? it makes no sense at all, just do `.exchangeToMono(response -> ...)` and handle your response inside the exchangeToMono function – Toerktumlare Dec 21 '21 at 18:57

2 Answers2

3

i have no idea why you are doing exchangeToMono(Mono::just).flatMap(response -> ...) as it makes no sense.

No you don't need to consume the Mono twice, what you need is to read the documentation on how to use the exchange function properly WebClient > Exchange

Here is the example taken from the docs

Mono<Person> entityMono = client.get()
        .uri("/persons/1")
        .accept(MediaType.APPLICATION_JSON)
        .exchangeToMono(response -> {
            if (response.statusCode().equals(HttpStatus.OK)) {
                return response.bodyToMono(Person.class);
            }
            else {
                // Turn to error
                return response.createException().flatMap(Mono::error);
            }
        });

And here is your code, but doing the things in the exchangeToMono instead

WebClient.create().post().uri(theUrl).bodyValue(p).exchangeToMono(response -> {
    if (response.statusCode().isError()) {
        // do something;
    }

    return response.bodyToMono(Pojo.class).single();
});

exchangeToMono has a purpose, its not there to just put stuff into a Mono<T> its there because you need to consume the response so that the server can release the connection that has been allocated. You do that by doing your checks etc in the exchangeToMono and then extract the body to a Mono<T>.

Toerktumlare
  • 12,548
  • 3
  • 35
  • 54
  • Great answer. I was not exactly doing `exchangeToMono(Mono::just).flatMap(response -> ...)` , but `exchangeToMono(Mono::just).elapsed().flatMap(durationAndResponse -> ...)` so with `durationAndResponse` I could print time elapsed, print response status, and (if needed) end with `return response.createException().flatMap(Mono::error);`, which inside had the response body for logging purposes. Now I understand if I don't read the body inside the exchangeToMono it would be to late. – Fradantim Dec 21 '21 at 21:05
  • also i have already answered this question sort of here https://stackoverflow.com/questions/64650820/spring-webflux-5-3-0-webclient-exchangetomono and i can only answer questions in regards to the code presented. If the code is not in the question, the answer is what it is. – Toerktumlare Dec 21 '21 at 21:45
1

The response Mono is empty because it has already been consumed by .exchangeToMono, thus single() operator fails with NoSuchElementException as expected.

lkatiforis
  • 5,703
  • 2
  • 16
  • 35
  • Thanks for answering. Is there a way to consume it twice? Strange thing is in the `exchangeToMono()` case if I call `response.createException()` the same method is executted, parsed as byte[] and (in the end) the body is retrieved. – Fradantim Dec 21 '21 at 18:18
  • @Fradantim you can not consume it twice. Put your logic inside `exchangeToMono` instead. – lkatiforis Dec 21 '21 at 19:17