1

I have a Java program that sends hundreds of GET requests to a server using Java 11 HttpClient, and it needs to do this 2-3 times every minute. Currently, it does the following (just describing the logic):

for each request that needs to be sent:

    //build GET request
    CompletableFuture<HttpResponse<String>> future = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
    futures.add(future)
    // for every 100 request, sleep for 2500 milliseconds (to wait for responses)

for each future in futures:
    // parse JSON response

The code works mostly fine, however, it is seriously impacted by the strength of the internet connection, as if it is bad, 80% of the requests don't get their responses, as the waiting time wasn't enough.

The question is if this is the correct way to do this, and if not, what would it be. Also, should I rather wait for all requests to get a response (like with sending a requests sinchronously and using .join()), and if yes, how can I do that.

cr0078
  • 23
  • 1
  • 8
  • Do you need to sleep? That would block the thread. Can you just fire your request and forget? So fire all 100, or do the calls depend on each other? – JCompetence Aug 09 '21 at 12:23
  • @SusanMustafa I am using sleep to wait for the responses of the requests, otherwise they wouldn't have time to get a response – cr0078 Aug 09 '21 at 12:26

1 Answers1

4

If all your API calls are independent then you can fire first, then you can join the results later.

Simple example with GET:

List<URI> uris  = ... //

HttpClient client = HttpClient.newHttpClient();
List<HttpRequest> requests = uris.stream()
        .map(HttpRequest::newBuilder)
        .map(reqBuilder -> reqBuilder.build())
        .collect(toList());

CompletableFuture.allOf(requests.stream()
        .map(request -> client.sendAsync(request, ofString()))
        .toArray(CompletableFuture<?>[]::new))
        .join();

You might benefit from using a custom executor as well:

private static final ExecutorService executorService = Executors.newFixedThreadPool(5);

private static final HttpClient httpClient = HttpClient.newBuilder()
        .executor(executorService)
        .version(HttpClient.Version.HTTP_2)
        .connectTimeout(Duration.ofSeconds(10))
        .build();

Edited----

If you want results, then allOf does not help as it actually returns a VOID.

static CompletableFuture allOf(CompletableFuture<?>... cfs)

Returns a new CompletableFuture that is completed when all of the given CompletableFutures complete.

Instead you can still use join, however in a different way:

List<CompleteFuture<HttpResponse<String>> listOfCompletableFutures = ...

     listOfCompletableFutures.
    .stream()
    .map(CompletableFuture::join)
    .filter(Objects::nonNull)
    .collect(Collectors.toList());

Good references:

https://openjdk.java.net/groups/net/httpclient/recipes.html

Java 11 HttpClient - What is Optimum Ratio of HttpClients to Concurrent HttpRequests

https://mkyong.com/java/java-11-httpclient-examples/

Java collecting results of CompletableFuture from multiple calls

JCompetence
  • 6,997
  • 3
  • 19
  • 26
  • Thank you, this helped a lot. One last question: if I need to handle the response body (parse the JSON), where do I do that? After the join? – cr0078 Aug 09 '21 at 15:12
  • Kindly see if the edited answer helps you, and please update! :) – JCompetence Aug 10 '21 at 08:27