Java 9: The New HttpClient

With JEP-110: HTTP/2 Client, Java 9 will “Define a new HTTP client API that implements HTTP/2 and WebSocket, and can replace the legacy HttpURLConnection API.”

The Problem

Java’s existing HttpURLConnection has some problems. It is hard to use, in part from being abstracted to work with multiple protocols (like FTP and Gopher). It turns out 20 years later that HTTP was the big winner in protocol land. But the biggest problem is that HttpURLConnection works only in blocking mode so there is only one thread per request/response.

There are other HTTP clients out there that solve some of these problems, such as Jetty and the Apache HttpClient. Jetty even has a non-blocking mode. However, both of these are sizable libraries to bring in, and neither take advantage of lambdas.

The Solution

Introducing: Java 9’s HttpClient!

The new API works with HTTP/2, deals with multiple responses (server push), can stream the request and/or response bodies, and can work with synchronous or asynchronous requests or responses. So there is a lot of flexibility to taylor the behavior of your client to the problem you’re trying to solve. If that was not enough, the API makes it very easy to work with your request and response bodies as strings, bytes, input streams, even directly as files.

The HttpRequest Javadocs are thorough and provide a number of examples demonstrating various use cases. After reading the Javadocs, you’ll find that you’ll really understand the API more fully when you try to use it yourself. The rest of this post will explore a specific use case that I wanted to accomplish as I learned the API myself.

An Example: Stream It

I had in mind a specific problem: What if I wanted to download a very large document in a response body that I didn’t want to save to disk, but instead wanted to parse as it was downloading? One could make the argument that downloading to a file and processing after the download is a better separation of concerns, and that it allows you to retry the download or retry the processing if something goes wrong in either step. But for sake of argument let’s say the response body is unreasonably huge, the client is on a device with limited space, etc.

To get started, we can obtain an HttpClient with the builder from HttpClient.create() which provides a fluent API for creating a client. If you are ok with the defaults you can get started a little more quickly with the HttpRequest.Builder obtained from HttpRequest.create(), which is what happens in the code below.

The important things to learn from this example are that:

  1. The possible arguments to HttpRequest.Builder.body() allow for reading the request body from a file or or other source. Using RequestBuilder.body(fromInputStream(…)) allows you to stream the request body.
  2. The corresponding .body() and .bodyAsync() of the HttpResponse allow us to optionally stream the response body, again into a String, file, etc
  3. The HttpRequest’s .response() and .responseAsync() allow us to optionally block on the response itself

The code shows this in action with a blocking non-streaming request and a non-blocking streaming response (although we can choose to block with .join()).

import java.io.*;
import java.net.http.*;
import java.net.URI;
import java.util.*;
import java.util.concurrent.*;

import static java.net.http.HttpRequest.*;
import static java.net.http.HttpResponse.*;

public class Main {

   public static void main(String[] args) throws Exception {

      requestStreaming("http://stackoverflow.com");

      System.out.println("Program done.");
      System.exit(0);
   }

   public static void requestStreaming(String url) throws Exception {

      HttpRequest request = HttpRequest.create(new URI(url))
                                       .body(noBody())
                                       .GET();

      request.response()
             .bodyAsync(HttpResponse.asInputStream())
             .thenAccept( s -> readBody(s))
             .join();
   }

   public static void readBody(InputStream stream) {

      try(BufferedReader br = new BufferedReader(new InputStreamReader(stream, "UTF-8"))) {
         String line = br.readLine();
         while(line != null) {
            processLine(line);
            line = br.readLine();
         }
      }
      catch(Exception e) {
         e.printStackTrace();
      }
      System.out.println("Stream processing Done!");
   }

   public static void processLine(String line) throws Exception {
      System.out.print(".");
   }
}

Conclusion

Java 9’s new HttpClient provides a lot of flexibility and incorporates the latest language features. I expect we will see lots of creative uses!

Advertisements

Leave a comment

Filed under Java 9

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s