19

I want to execute 3 calls simultaneously and process the results once they're all done.

I know this can be achieved using AsyncRestTemplate as it is mentioned here How to use AsyncRestTemplate to make multiple calls simultaneously?

However, AsyncRestTemplate is deprecated in favor of WebClient. I have to use Spring MVC in the project but interested if I can use a WebClient just to execute simultaneous calls. Can someone advise how this should be done properly with WebClient?

ddzz
  • 259
  • 1
  • 3
  • 10

4 Answers4

17

Assuming a WebClient wrapper (like in reference doc):

@Service
public class MyService {

    private final WebClient webClient;

    public MyService(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.baseUrl("http://example.org").build();
    }

    public Mono<Details> someRestCall(String name) {
        return this.webClient.get().url("/{name}/details", name)
                        .retrieve().bodyToMono(Details.class);
    }

}

..., you could invoke it asynchronously via:

// ... 
  @Autowired
  MyService myService
  // ...

   Mono<Details> foo = myService.someRestCall("foo");
   Mono<Details> bar = myService.someRestCall("bar");
   Mono<Details> baz = myService.someRestCall("baz");
   
   // ..and use the results (thx to: [2] & [3]!):

   // Subscribes sequentially:

   // System.out.println("=== Flux.concat(foo, bar, baz) ===");
   // Flux.concat(foo, bar, baz).subscribe(System.out::print);
    
   // System.out.println("\n=== combine the value of foo then bar then baz ===");
   // foo.concatWith(bar).concatWith(baz).subscribe(System.out::print);
  
   // ----------------------------------------------------------------------
   // Subscribe eagerly (& simultaneously):
   System.out.println("\n=== Flux.merge(foo, bar, baz) ===");
   Flux.merge(foo, bar, baz).subscribe(System.out::print);

Thanks, Welcome & Kind Regards,

xerx593
  • 12,237
  • 5
  • 33
  • 64
  • in this case there should be a thread pool for all async service calls that should be done. But isn't it possible to use Webclient Nio capabilities, fire all requests and then grab all Mono to compose the result. In my understanding Mono is the same as ListenableFuture and get() is the same as block(), so it should work with WebClient the same way it works with AsyncRestTemplate – ddzz May 06 '18 at 20:56
  • 5
    @xerx593 please update your answer with `Flux.merge`, as `Flux.concat` subscribes sequentially (REST calls won't happen at the same time) - `Flux.merge` eagerly subscribes to all publishers. – Brian Clozel May 07 '18 at 07:31
  • @ddzz `Mono` and `ListenableFuture` are not comparable here, neither are `WebClient` and `AsyncRestTemplate` so I'm not sure what you mean. Besides the choice of operator, this answer looks right to me. If you've got a question about the runtime model behind `WebClient`, please ask another question. – Brian Clozel May 07 '18 at 07:34
  • This does not return the response immediately though. I tried this and the response prints after client has already returned response – TrollBearPig Nov 01 '22 at 03:13
6

You can make HTTP calls concurrently using simple RestTemplate and ExecutorService:

RestTemplate restTemplate = new RestTemplate();
ExecutorService executorService = Executors.newCachedThreadPool();

Future<String> firstCallFuture = executorService.submit(() -> restTemplate.getForObject("http://first-call-example.com", String.class));
Future<String> secondCallFuture = executorService.submit(() -> restTemplate.getForObject("http://second-call-example.com", String.class));

String firstResponse = firstCallFuture.get();
String secondResponse = secondCallFuture.get();

executorService.shutdown();

Or

Future<String> firstCallFuture = CompletableFuture.supplyAsync(() -> restTemplate.getForObject("http://first-call-example.com", String.class));
Future<String> secondCallFuture = CompletableFuture.supplyAsync(() -> restTemplate.getForObject("http://second-call-example.com", String.class));

String firstResponse = firstCallFuture.get();
String secondResponse = secondCallFuture.get();
Justinas Jakavonis
  • 8,220
  • 10
  • 69
  • 114
6

You can use Spring reactive client WebClient to send parallel requests. In this example,

public Mono<UserInfo> getUserInfo(User user) {
        Mono<UserInfo> userInfoMono = getUserInfo(user.getId());
        Mono<OrgInfo> organizationInfoMono = getOrgInfo(user.getOrgId());

        return Mono.zip(userInfoMono, organizationInfoMono).map(tuple -> {
            UserInfo userInfo = tuple.getT1();
            userInfo.setOrganization(tuple.getT2());
            return userInfo;
        });
    }

Here:

  • getUserInfo makes an HTTP call to get user info from another service and returns Mono
  • getOrgInfo method makes HTTP call to get organization info from another service and returns Mono
  • Mono.zip() waits all the results from all monos and merges into a new mono and returns it.

Then, call getOrgUserInfo().block() to get the final result.

Sarvar Nishonboyev
  • 12,262
  • 10
  • 69
  • 70
1

Another way:

public Mono<Boolean> areVersionsOK(){
        final Mono<Boolean> isPCFVersionOK = getPCFInfo2();
        final Mono<Boolean> isBlueMixVersionOK = getBluemixInfo2();

        return isPCFVersionOK.mergeWith(isBlueMixVersionOK)
            .filter(aBoolean -> {
                return aBoolean;
            })
            .collectList().map(booleans -> {
                return booleans.size() == 2;
        });

    }
jabrena
  • 1,166
  • 3
  • 11
  • 25