Introduction to the JDK HTTP Client
 
By Chris Hegarty

The JDK HTTP Client was added in JDK 9. It can be used to request HTTP resources over the network. It supports HTTP/1.1 and HTTP/2, both synchronous and asynchronous programming models, handles request and response bodies as reactive-streams, and follows the familiar builder pattern.

Here's a basic request that prints the response body as a String.

    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
          .uri(URI.create("http://openjdk.java.net/"))
          .build();
    client.sendAsync(request, asString())
          .thenApply(HttpResponse::body)
          .thenAccept(System.out::println)
          .join();
Incubation

The API currently has an Incubating status, which is reflected in its module and package name. The intent is to incubate the API and then move it towards standardization in a future version of Java SE. More information on incubation can be found in JEP 11, but for now it's sufficient to recognise the package name, and know how to get access to the API by specifying its containing module, jdk.incubator.httpclient, to the --add-modules command line option. The Maven compiler and surefire plugins can be configured as follows:

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>9</source>
                    <target>9</target>
                    <compilerArgument>--add-modules=jdk.incubator.httpclient</compilerArgument>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.20.1</version>
                <configuration>
                    <argLine>--add-modules=jdk.incubator.httpclient</argLine>
                </configuration>
            </plugin>
        </plugins>
    </build>

HttpClient

To send a request, first create an HttpClient from its builder. The builder can be used to configure per-client state, like:

  • The preferred protocol version ( HTTP/1.1 or HTTP/2 )
  • Whether to follow redirects
  • A proxy
  • An authenticator
    HttpClient client = HttpClient.newBuilder()
          .version(Version.HTTP_2)
          .followRedirects(Redirect.SAME_PROTOCOL)
          .proxy(ProxySelector.of(new InetSocketAddress("www-proxy.com", 8080)))
          .authenticator(Authenticator.getDefault())
          .build();

Once built, an HttpClient can be used to send multiple requests.

HttpRequest

An HttpRequest is created from its builder. The request builder can be used to set:

  • The request URI
  • The request method ( GET, PUT, POST )
  • The request body ( if any )
  • A timeout
  • Request headers
    HttpRequest request = HttpRequest.newBuilder()
          .uri(URI.create("http://openjdk.java.net/"))
          .timeout(Duration.ofMinutes(1))
          .header("Content-Type", "application/json")
          .POST(BodyPublisher.fromFile(Paths.get("file.json")))
          .build()

Once built an HttpRequest is immutable, and can be sent multiple times.

Synchronous or Asynchronous

Requests can be sent either synchronously or asynchronously. The synchronous API, as expected, blocks until the HttpResponse is available.

    HttpResponse<String> response = client.send(request, BodyHandler.asString());
    System.out.println(response.statusCode());
    System.out.println(response.body());

The asynchronous API returns immediately with a CompletableFuture that completes with the HttpResponse when it becomes available. CompletableFuture was added in Java 8 and supports composableasynchronous programming.

    client.sendAsync(request, BodyHandler.asString())
          .thenApply(response -> { System.out.println(response.statusCode());
                                   return response; } )
          .thenApply(HttpResponse::body)
          .thenAccept(System.out::println);
Data as reactive-streams

The request and response bodies are exposed as reactive streams ( asynchronous streams of data with non-blocking back pressure.) The HttpClient is effectively a Subscriber of request body and a Publisher of response body bytes. The BodyHandler interface allows inspection of the response code and headers, before the actual response body is received, and is responsible for creating the response BodySubscriber.

    public abstract class HttpRequest {
        ...
        public interface BodyPublisher
                    extends Flow.Publisher<ByteBuffer> { ... }
    }

    public abstract class HttpResponse<T> {
        ...
        public interface BodyHandler<T> {
            BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders);
        }

        public interface BodySubscriber<T>
                    extends Flow.Subscriber<List<ByteBuffer>> { ... }
    }

The HttpRequest and HttpResponse types provide a number of convenience factory methods for creating request publishers and response subscribers for handling common body types such as files, Strings, and bytes. These convenience implementations either accumulate data until the higher-level Java type can be created, like a String, or stream the data in the case of a file. The BodySubscriber and BodyPublisher interfaces can be implemented for handling data as a custom reactive stream.

    HttpRequest.BodyPublisher::fromByteArray(byte[])
    HttpRequest.BodyPublisher::fromByteArrays(Iterable)
    HttpRequest.BodyPublisher::fromFile(Path)
    HttpRequest.BodyPublisher::fromString(String)
    HttpRequest.BodyPublisher::fromInputStream(Supplier< InputStream>)

    HttpResponse.BodyHandler::asByteArray()
    HttpResponse.BodyHandler::asString()
    HttpResponse.BodyHandler::asFile(Path)
    HttpResponse.BodyHandler::discard()

Adapters between java.util.concurrent.Flow's Publisher/Subscriber types to the HTTP Client's BodyPublisher/BodySubscriber types have been added in JDK 10, see JDK-8193366 for further details.

HTTP/2

The JDK HTTP Client supports both HTTP/1.1 and HTTP/2. By default the client will send requests using HTTP/2. Requests sent to servers that do not yet support HTTP/2 will automatically be downgraded to HTTP/1.1. Here's a summary of the major improvements that HTTP/2 brings:

  • Header Compression. HTTP/2 uses HPACK compression, which reduces overhead.
  • Single Connection to the server, reduces the number of round trips needed to set up multiple TCP connections.
  • Multiplexing. Multiple requests are allowed at the same time, on the same connection.
  • Server Push. Additional future needed resources can be sent to a client.
  • Binary format. More compact.

Since HTTP/2 is the default preferred protocol, and the implementation seamlessly fallbacks to HTTP/1.1 where necessary, then the JDK HTTP Client is well positioned for the future, when HTTP/2 is more widely deployed.

The Future

The incubating JDK HTTP Client has been updated in JDK 10 as a result of community feedback. Additional convenience request body publishers and response body subscribers have been added. The implementation has been rewritten to be fully asynchronous.

JEP 321 has been filed to Standarize the HTTP Client in Java SE. This means that the jdk.incubator module and package prefix will be dropped in favour of something in the java.net namespace. The additional --add-modules command line option will no longer be needed ( the module will be available by default ).

References
Join the Database Community Conversation
DEVO_ATTACH_BOTTOM
Experience Oracle Cloud —Get up to 3,500 hours free.