Kieran France Kieran France is a programmer for IDRSolutions in charge of internal Java testing. In his spare time he enjoys tinkering with gadgets and code.

Why HTTP/2 Client in Java 9 is important

4 min read

Http

In our previous Java 9 series article we looked at the changes to Unicode support in Java 9. This time we will be looking at HTTP/2 support.

With the introduction of Java 9 we are receiving many new features and improvements. One of these is the introduction of the class HTTPClient which supports HTTP/2. Currently there are libraries out there that can provide support for this but the intention here is to introduce HTTP/2 support natively via a new API for handling HTTP connections.

HTTP/2?

I have mentioned HTTP/2 a couple of times already but you may not know what this is. HTTP has been in use for quite some time with HTTP/1.1, which came about in 1997, being the version that is in common use. In 2015 HTTP/2 was standardised and there are many differences and improvements. Whilst I touch on some of these improvements later on the full list of changes is too long to fully discuss here. If you want to read some more about HTTP/2 you will find more information on the Wikipedia page.

What changes are being made?

The original HTTP handling API in Java was written back when HTTP/1.1 was a new shiny thing. It was written to be agnostic, supporting many different types of connections using the same API. Over time the uses of HTTP have evolved but the Java API has not kept pace with it. So for Java 9 a new API been introduced that is cleaner and clearer to use and which also adds support for HTTP/2.

The new API handles http connections via 3 classes.

  • HttpClient which handles the creation and send of requests.
  • HttpRequest which is used to construct a request to be sent via the HttpClient.
  • HttpResponse which holds the response from the request that has been sent.

To make a request it is as simple as getting your client, building a request and sending it as shown below.

HttpClient httpClient = HttpClient.newHttpClient(); //Create a HttpClient
HttpRequest httpRequest = HttpRequest.newBuilder().uri(new URI("https://www.google.com/")).GET().build(); //Create a GET request for the given URI
HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandler.asString()); //Send the request that will decode the body as a String
System.out.println(httpResponse.body()); //Output the body of the response

For comparison here is the same functionality in the current API.

URL url = new URL("https://www.google.com/"); //Specify the URL
URLConnection urlConnection = url.openConnection(); //Create the connection
BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //Create input stream and load into reader
String inputLine;
while ((inputLine = reader.readLine()) != null) { //Loop through and output each line in stream.
    System.out.println(inputLine);
}
reader.close(); //Close Reader

As you can see the new API takes half the number of lines and in my opinion it is much clearer what is happening in the top example than the bottom.

But what else can | do?

In the above code the top example uses httpClient.send(…) which will block until it has completed. This is not always desirable, if only we could send request and then check in for a response from that request later, calling it in an asynchronous manner. Well you can.

CompletableFuture<HttpResponse<String>> httpResponse = httpClient.sendAsync(httpRequest, HttpResponse.BodyHandler.asString()); //Send the request asynchronously

The request continues in the background allowing the code to continue on to other tasks. The returned CompletableFuture object can be used to determine whether the request has been completed yet and provide you access to the HttpResponse once it is complete. Should you desire you can even cancel the request before it completes.

if(httpResponse.isDone()) {
    System.out.println(httpResponse.get().statusCode());
    System.out.println(httpResponse.get().body());
} else {
    httpResponse.cancel(true);
}

Should you wish to receive a response as something other than a String there are BodyHandlers for that such as the ones below. As shown earlier these are used as a second input to the HttpClient.send and HttpClient.sendAsync methods.

HttpResponse.BodyHandler.asByteArray() //Body is handled as byte[]
HttpResponse.BodyHandler.asByteArrayConsumer(Consumer) //Body is handled as byte[] sent to a Consumer
HttpResponse.BodyHandler.asFile(Path file) //Body is written to the file specified
HttpResponse.BodyHandler.discard(Object input) //Body is discarded and replaced with input
HttpResponse.BodyHandler.asString() //Body is decoded as a String

Now that works really nicely for the basics, but what if you wish to add further configurations to you request or you client? Well that is what we have builders for.

Where are my options?

In the first example you will have seen HttpRequest.newBuilder(). This creates a builder which is used to build the request (there is a similar method for HttpClient). The builder can be chained to create a request or client as we did in the first example. To see what options can be set you just need to look at the HttpClient.Builder and the HttpRequest.Builder classes, the methods are self explanatory.

// HttpClient.Builder
public abstract static class Builder { //Following list contains only the methods of the class 
    public abstract Builder cookieManager(CookieManager cookieManager); 
    public abstract Builder sslContext(SSLContext sslContext); 
    public abstract Builder sslParameters(SSLParameters sslParameters); 
    public abstract Builder executor(Executor executor); 
    public abstract Builder followRedirects(Redirect policy); 
    public abstract Builder version(HttpClient.Version version); 
    public abstract Builder priority(int priority); 
    public abstract Builder proxy(ProxySelector selector); 
    public abstract Builder authenticator(Authenticator a); 
    public abstract HttpClient build(); 
}
// HttpRequest.Builder
public abstract static class Builder { //Following list contains only the methods of the class 
    public abstract Builder uri(URI uri); 
    public abstract Builder expectContinue(boolean enable); 
    public abstract Builder version(HttpClient.Version version); 
    public abstract Builder header(String name, String value); 
    public abstract Builder headers(String... headers); 
    public abstract Builder timeout(Duration duration); 
    public abstract Builder setHeader(String name, String value); 
    public abstract Builder GET(); 
    public abstract Builder POST(BodyProcessor body); 
    public abstract Builder PUT(BodyProcessor body); 
    public abstract Builder DELETE(BodyProcessor body); 
    public abstract Builder method(String method, BodyProcessor body); 
    public abstract HttpRequest build(); 
    public abstract Builder copy(); 
}

In general these new methods have been introduced in order to make using this functionality much easier and allows the code to be more readable.

And why is this important?

The original HTTP connection API came about around the same time as the HTTP/1.1 specification back in 1997. Over the last 20 years the way the internet is used has changed. To accommodate this HTTP and the way it is used has been improving to try and keep up with the demand. Unfortunately the Java API for handling HTTP had not been keeping up with the pace.

With the introduction of the new API we now have simple ways to create HTTP connections that will be easier to maintain, faster and can allow for a more responsive application without the need for third party libraries.



Our software libraries allow you to

Convert PDF to HTML in Java
Convert PDF Forms to HTML5 in Java
Convert PDF Documents to an image in Java
Work with PDF Documents in Java
Read and Write AVIF, HEIC, WEBP and other image formats
Kieran France Kieran France is a programmer for IDRSolutions in charge of internal Java testing. In his spare time he enjoys tinkering with gadgets and code.