Search code examples
javaspring-webfluxproject-reactorspring-webclientpublisher

Is there a way to cache responses using Mono from a WebClient call?


Is there a way to cache the response from a call if the inputs are the same? For example, if the fileName is the same, I don't want to make the request out with my webClient again since it takes a while for the response.

Flux.fromIterable(files).flatMap(
  file -> {
     Mono<String> extension = getExtension(file.getName());
   });

public Mono<String> getExtension(String fileName) {
  return webClient.get().uri(extensionUrl + "?file=" + fileName).retrieve()
    .bodyToMono(String[].class)
    .map(
      extensions -> {
        System.out.println("finished retrieving");
        return extensions[0];
      });
}

Solution

  • Reactor doesn't provide any caching layer, but you can use any existing library or a plain Map for caching, then check for an existing value before making the call. And put one on doOnNext.

    Caching an original Mono itself may not work because it depends on actual implementation and in many cases a new subscription to it will cause a new request (but see below).

    So the simplest implementation would be:

    private Map<String, String> cache = new ConcurrentHashMap();
    
    public Mono<String> getExtension(String fileName) {
       String existing = cache.get(fileName);
       if (existing != null) {
           return Mono.just(existing);
       }
    
       return webClient.get().uri(extensionUrl + "?file=" + fileName).retrieve()
           .bodyToMono(String[].class)
           .map(....)
           .doOnNext(value -> cache.put(fileName, value));
    }
    

    If you want to avoid duplicate in-flight requests as well, then it makes sense to cache the Mono. But in this case I would suggest to ether use it with .cache(), or even use a Sinks.One as an intermediary.

    The simplified code would look like:

    private Map<String, Mono<String>> cache = new ConcurrentHashMap();
    
    public Mono<String> getExtension(String fileName) {
       Mono<String> existing = cache.get(fileName);
       if (existing != null) {
           return existing;
       }
    
       Mono<String> requested = webClient.get().uri(extensionUrl + "?file=" + fileName).retrieve()
           .bodyToMono(String[].class)
           .map(....)
           // you probably don't want to keep failed requests in cache
           .doOnError(t -> cache.remove(fileName))
           .cache();
       cache.put(fileName, requested);
    
       return requested;
    }