2

I am currently using Spring WebFlux to try build an async end-point, which fetches a PDF from a third-party end-point via Web Client before returning the PDF back to our API consumer. However, I am struggling with returning a Mono<ResponseEntity> with content type application/pdf due to the below exception:

Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class reactor.core.publisher.MonoMapFuseable] with preset Content-Type 'application/pdf']

Here is controller implementation. My question is:

  • Is my implementation in the right direction, or would I need to create some sort of converter?
  • Does Mono<ResponseEntity> even support returning a PDF as a response body?
    @RequestMapping(value="/get-pdf", method = RequestMethod.GET)
    public Mono<ResponseEntity> getPDFAsync() {

        String url = "http://some-end-point";
        WebClient client = WebClient.create(url);

        return client.get()
                .accept(MediaType.APPLICATION_PDF)
                .exchangeToMono(response ->
                        Mono.just(ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF)
                            .body(response.bodyToMono(ByteArrayResource.class)
                                    .map(byteArrayResource -> byteArrayResource.getByteArray())
                        )));
    }
Matthew Lau
  • 59
  • 1
  • 9
  • Does this answer your question? [Return generated pdf using spring MVC](https://stackoverflow.com/questions/16652760/return-generated-pdf-using-spring-mvc) – Toerktumlare Aug 07 '21 at 12:43
  • @Toerktumlare that wouldn't be a very reactive way to do it, see my answer for one possible solution. – adnan_e Aug 07 '21 at 13:38
  • I did not claim it to be a complete served answer, but it gives you a guide in how it is done and then you can adapt it to reactive. For instance, your second question you look in the link i posted, they transfer a byte array, and webflux has support for transfering streams of bytes. – Toerktumlare Aug 07 '21 at 16:19

2 Answers2

2

To download a file reactively, you could supply the file as a Flux<DataBuffer>, where DataBuffer is org.springframework.core.io.buffer.DataBuffer, like this:

    // some shared buffer factory.
    private final DataBufferFactory dataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);



    @RequestMapping(value = "/download",
            method = RequestMethod.GET,
            produces = {MediaType.APPLICATION_PDF_VALUE}
    )
    public Mono<ResponseEntity<Flux<DataBuffer>>> downloadDocument(
            ...
    ) {
        return Mono.fromCallable(() -> {
           return ResponseEntity.ok(
             DataBufferUtils.read(
               new File("somepdf.pdf").toPath(), 
               dataBufferFactory, 
               8096
           ))
        });
    }

Or more specifically, since you seem to be using the WebFlux WebClient, you can forward the response body flux directly to your own response, without having to buffer the complete response first:

    @RequestMapping(value = "/download",
            method = RequestMethod.GET,
            produces = {MediaType.APPLICATION_PDF_VALUE}
    )
    public Mono<ResponseEntity<Flux<DataBuffer>>> downloadDocument(
            ...
    ) {
          String url = "http://some-end-point";
          WebClient client = WebClient.create(url);

          return client.get()
                  .accept(MediaType.APPLICATION_PDF)
                  .exchange()
                  .map(response -> response.bodyToFlux(DataBuffer.class))
                  .map(ResponseEntity::ok);
    }

Hint: I hope you are reusing the WebClient instance and not instantiating a new one on each request.

adnan_e
  • 1,764
  • 2
  • 16
  • 25
  • This still gives the error "No converter", etc., etc. – user3067860 Sep 21 '21 at 01:33
  • @user3067860 Are you sure you have the correct dependencies for WebFlux? This should work out of the box on any webflux project, but wouldn't work on spring MVC with reactor dependencies. I had cases where i had a spring MVC with reactor libraries and had to write a custom converter. If that is the case for you, feel free to post a new question and let me know, and I will send you my converter. – adnan_e Sep 21 '21 at 08:28
-1

I have found the answer! In short, returning Mono<byte[]>, and add produces = {MediaType.APPLICATION_PDF_VALUE} to @RequestMapping works. See example below.

    @RequestMapping(value="/get-pdf",  produces = {MediaType.APPLICATION_PDF_VALUE},  method = RequestMethod.GET)
public Mono<byte[]> getPdf() {
    
    String url = "some-end-point";
    WebClient client = WebClient.create(url);

    return client.get()
            .accept(MediaType.APPLICATION_PDF)
            .exchangeToMono(response -> response
                    .bodyToMono(ByteArrayResource.class))
            .map(byteArrayResource -> byteArrayResource.getByteArray());
}
Matthew Lau
  • 59
  • 1
  • 9