Asynchronous HttpClient

Wouldn’t it be great if there was any HTTP API that doesn’t block until an HttpResponse is available? I mean, a main thread that can execute other tasks in parallel and doesn’t block or wait for the completion of that task? Well, there is one.

java.net.http.HttpClient provides both synchronous and asynchronous request mechanisms. The synchronous mechanism was explained in the first article of this blog post series.

HttpClient.sendAsync is the answer to the question. It sends a request and receives the response asynchronously. This method returns a CompletableFuture.

I will focus on CompletableFuture.

Let’s analyze an example of it:

HttpClient httpClient = HttpClient.newBuilder().build();
HttpRequest mainRequest =
        HttpRequest.newBuilder()
                .uri(URI.create("http://localhost:8080/hello"))
                .build();

CompletableFuture<HttpResponse<String>> responseFuture =
        httpClient.sendAsync(mainRequest, BodyHandlers.ofString());

responseFuture.whenComplete((response, error) -> {
  if (response != null) {
    System.out.println(response.body());
  }
  if (error != null) {
    error.printStackTrace();
  }
});

System.out.println(responseFuture.join().body());

The HttpClient is generated by a builder which is used to configure the per-client state. Once built, the HttpClient is immutable and can be used to send multiple requests. You’ll find more information about it and its builder methods in my first article about HttpClient.

In the above example, sendAsync returns a java.util.concurrent.CompletableFuture<HttpResponse<String>> object. CompletableFuture implements the two interfaces Future and CompletionStage. It offers a lot of methods to create, combine or chain multiple Future objects.

In the example above, I used the method whenComplete(BiConsumer<? super T,? super Throwable> action). This method returns a new CompletionStage that contains the same result. In case of an exception the CompletionStage will contain that exception. But there are more methods like this one. Two methods which I consider important are thenAccept(Consumer<? super T> action) and thenApply(Function<? super T,? extends U> fn). The first one returns a CompletableFuture<Void> (a new CompletionStage). This stage is executed with this stage’s result if it is completed normally. The return value of this is void because it is expected to have finalized processing the content in that specified Consumer, but it is still a CompletableFuture because one can wait for it to finish. The second one thenApply(Function<? super T,? extends U> fn) is also very important. It returns a new CompletableFuture<U>. This returned CompletableFuture is executed with the current stage’s result as the argument to the supplied function, when the current stage completes normally.

There are a lot more methods provided by CompletableFuture. You can find a documentation of all of them in the Oracle docs.

The request and response bodies are represented as reactive streams. These are asynchronous streams of data that contain non-blocking back pressure. So to handle request and response bodies, the HTTP/2 API uses reactive streams. They can build pipelines similar to Java 8 streams.

The CompletableFuture is actually the response that will be returned at a later time by the server. Once the server returns a response, the CompletableFuture is completed.

With the HttpClient class Java applications can not only execute HTTP requests but also asynchronously process HTTP responses. This API also supports the WebSocket protocol, which will be the subject of the next blog post.

I hope you could pick up some useful information of the above presented concept. This article is part of a blog post series and more info is already on its way.

Short URL for this post: https://wp.me/p4nxik-3CK
This entry was posted in Java Basics and tagged , , . Bookmark the permalink.

Leave a Reply