Search code examples
javaresthttpclient

External service call with Java 11 HttpClient sync vs async


My microservice is calling an external service POST call and I want to use Java 11 Httpclient. Here how shall the send() and sendAsync() methods can make difference? I have tested with multiple amount of request, almost same latency. I tried executing 100 endpoint call for my service with 10 or 20 thread or more. The result for both methods are almost same.

I use sendAsync() with thenApply().get in response receive. I would like to know what is preferred way and why? Is using async is also fast(which is not as per my current result)?

Thanks in advance for your answers!


Solution

  • Here's a test of both methods:

    import java.net.URI;
    import java.net.http.HttpClient;
    import java.net.http.HttpRequest;
    import java.net.http.HttpRequest.Builder;
    import java.net.http.HttpResponse;
    import java.net.http.HttpResponse.BodyHandlers;
    import java.time.Duration;
    import java.util.List;
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.Executors;
    import java.util.function.Function;
    import java.util.function.Supplier;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    
    public class HttpClientTest {
        static final int REQUEST_COUNT = 100;
        static final String URI_TEMPLATE = "https://jsonplaceholder.typicode.com/posts/%d";
    
        public static void main(final String[] args) throws Exception {
            final List<HttpRequest> requests = IntStream.rangeClosed(1, REQUEST_COUNT)
                    .mapToObj(i -> String.format(URI_TEMPLATE, i))
                    .map(URI::create)
                    .map(HttpRequest::newBuilder)
                    .map(Builder::build)
                    .collect(Collectors.toList());
            final HttpClient client = HttpClient.newBuilder()
                    .executor(Executors.newFixedThreadPool(REQUEST_COUNT))
                    .build();
            final ThrowingFunction<HttpRequest, String> sendSync =
                request -> client.send(request, BodyHandlers.ofString()).body();
            final ThrowingFunction<CompletableFuture<HttpResponse<String>>, String> getSync =
                future -> future.get().body();
            benchmark("sync", () -> requests.stream()
                    .map(sendSync)
                    .collect(Collectors.toList()));
            benchmark("async", () -> requests.stream()
                    .map(request -> client.sendAsync(request, BodyHandlers.ofString()))
                    .collect(Collectors.toList()) // materialize to send the requests
                    .stream()
                    .map(getSync)
                    .collect(Collectors.toList()));
        }
    
        static void benchmark(final String name, final Supplier<List<String>> supplier) {
            new Thread(() -> {
                final long start = System.nanoTime();
                System.out.printf("%s: start%n", name);
                final List<String> result = supplier.get();
                final Duration duration = Duration.ofNanos(System.nanoTime() - start);
                final int size = result.stream()
                        .mapToInt(String::length)
                        .sum();
                System.out.printf("%s: end, got %d chars, took %s%n", name, size, duration);
            }, name).start();
        }
    
        @FunctionalInterface
        static interface ThrowingFunction<T, R> extends Function<T, R> {
            default R apply(final T t) {
                try {
                    return applyThrowing(t);
                } catch (final Exception e) {
                    throw new RuntimeException(e);
                }
            }
    
            R applyThrowing(T t) throws Exception;
        }
    }
    
    

    Example output:

    sync: start
    async: start
    async: end, got 26118 chars, took PT1.6102532S
    sync: end, got 26118 chars, took PT4.3368509S
    

    The higher the parallelism level of the API, the better the asynchronous method will perform.