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!
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.