Introduction to Java 11 Standarized HTTP Client API

The HTTP Client API is now part of the Java SE 11 standard. The module name and the package name of the standard API is java.net.http. The new APIs provide high-level client interfaces to HTTP (versions 1.1 and 2) and low-level client interfaces to WebSocket.

The following are the types in the API:

Classes

Interfaces

  • java.net.http.HttpClient.Builder
  • java.net.http.HttpRequest.BodyPublisher
  • java.net.http.HttpRequest.Builder
  • java.net.http.HttpResponse<T>
  • java.net.http.HttpResponse.BodyHandler<T>
  • java.net.http.HttpResponse.BodySubscriber<T>
  • java.net.http.HttpResponse.PushPromiseHandler<T>
  • java.net.http.HttpResponse.ResponseInfo
  • java.net.http.WebSocket
  • java.net.http.WebSocket.Builder
  • java.net.http.WebSocket.Listener

Enumeration

  • java.net.http.HttpClient.Redirect
  • java.net.http.HttpClient.Version

Exception

  • java.net.http.HttpTimeoutException
  • java.net.http.WebSocketHandshakeException

The HttpClient class (java.net.http.HttpClient)

An HttpClient can be used to send requests and retrieve their responses. An HttpClient provides configuration information, and resource sharing, for all requests sent through it.

An HttpClient is created through a 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, etc. Once built, an HttpClient is immutable, and can be used to send multiple requests.

HttpClient httpClient = HttpClient.newBuilder() .version(Version.HTTP_2) // default .build();

The builder provide method chaining to set several configuration. To set redirection strategy, set the followRedirects method. To set a proxy for the request, the builder method proxy is used to provide a ProxySelector

HttpClient httpClient = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.NORMAL) // Always redirect, except from HTTPS URLs to HTTP URLs. .proxy(ProxySelector.getDefault()) .build();

Requests can be sent either synchronously or asynchronously:

  • send(HttpRequest, BodyHandler) blocks until the request has been sent and the response has been received.
  • sendAsync(HttpRequest, BodyHandler) sends the request and receives the response asynchronously. The sendAsync method returns immediately with a CompletableFuture<HttpResponse>. The CompletableFuture completes when the response becomes available. The returned CompletableFuture can be combined in different ways to declare dependencies among several asynchronous tasks.

The HttpRequest class (java.net.http.HttpRequest)

An HttpRequest encapsulates an HTTP request, including the target URI, the method (GET, POST, etc), headers and other information.

An HttpRequest instance is built through an HttpRequest builder, which is obtained from one of the newBuilder methods. Once all required parameters have been set in the builder, build will return the HttpRequest. Builders can be copied and modified many times in order to build multiple related requests that differ in some parameters.

HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://www.base64code.com/encode/explain/HttpRequest")) .GET() // default .build();

To create a request that has a body in it (ex: POST, PUT and PATCH), a BodyPublisher is required in order to convert the source of the body into bytes

HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://restapi.programmingwith.com")) .POST(BodyPublishers.ofString(json)) .build();

or another example for PATCH:

HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://restapi.programmingwith.com/books/1")) .header("Content-type", "application/json") .method("PATCH", BodyPublishers.ofString(json)) .build();

The HttpResponse<T> interface (java.net.http.HttpResponse)

An HttpResponse is not created directly, but rather returned as a result of sending an HttpRequest. An HttpResponse is made available when the response status code and headers have been received, and typically after the response body has also been completely received. Whether or not the HttpResponse is made available before the response body has been completely received depends on the BodyHandler provided when sending the HttpRequest.

There are two ways of sending a request: either synchronously (blocking until the response is received) or asynchronously (non blocking).

For synchronous mode, we need invoke the send() method on the HTTP client, passing the request instance and a BodyHandler. If you read my previous article, Java 11 Standard HTTP Client VS Apache HttpClient, the BodyHandler built-in implementations allowing as to immediately parse the body of a response (As in majority case in sync calls). Using BodyHandlers.ofString(), below is an example of sync call that receives a response with the body as a string:

HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://www.base64code.com/about")) .build(); HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString()); System.out.println("status code: " + response.statusCode()); System.out.println("headers: " + response.headers()); System.out.println("body: " + response.body());

To avoid blocking until the response is returned by the server, sometimes it's useful to do call asynchronously. In this case, we can call the method sendAsync() .

HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://www.onlinefreeconverter.com/about")) .build(); httpClient.sendAsync(request, BodyHandlers.ofString()) .thenAccept(response -> { System.out.println("status code: " + response.statusCode()); System.out.println("headers: " + response.headers()); System.out.println("body: " + response.body()); });

Method sendAsync() will returns a CompletableFuture. A CompletableFuture provides a mechanism to chain subsequent actions to be triggered when it is completed. In sendAsync() , the returned CompletableFuture is completed when an HttpResponse is received. So above we can change above example into more "details" like:

HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://www.onlinefreeconverter.com/about")) .build(); CompletableFuture<HttpResponse<String>> response = httpClient.sendAsync(request, BodyHandlers.ofString()); String body = response.thenApply(HttpResponse::body).get(); HttpHeaders headers = response.thenApply(HttpResponse::headers).get(); int statusCode = response.thenApply(HttpResponse::statusCode).get(); System.out.println("status code: " + statusCode); System.out.println("headers: " + headers); System.out.println("body: " + body);

The WebSocket interface (java.net.http.WebSocket)

The WebSocket protocol enables interaction between a web browser (or other client application) and a web server with lower overheads, facilitating real-time data transfer from and to the server. This is made possible by providing a standardized way for the server to send content to the client without being first requested by the client, and allowing messages to be passed back and forth while keeping the connection open. In this way, a two-way ongoing conversation can take place between the client and the server.

The HTTP client also supports the WebSocket protocol, which also can makes use of asynchronous calls that return CompletableFuture. JEP321WebSocket is an example of how to use an HttpClient to create a WebSocket that connects to an echo server (ws://echo.websocket.org).

JEP321WebSocket.java
package com.dariawan.jdk11;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.net.http.WebSocket.Builder;
import java.net.http.WebSocket.Listener;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletionStage;

public class JEP321WebSocket {

    public static void main(String[] args) throws Exception {
        HttpClient httpClient = HttpClient.newBuilder().build();
        Builder webSocketBuilder = httpClient.newWebSocketBuilder();
        WebSocket webSocket = webSocketBuilder.buildAsync(URI.create("ws://echo.websocket.org"), new WebSocketListener()).join();
        
        webSocket.sendPing(ByteBuffer.wrap("Ping from Dariawan".getBytes()));
        
        webSocket.sendText("Rock it with HTML5 WebSocket", true);
        webSocket.sendText("Send text against the echo server", false);         
        webSocket.sendText("Using Java 11 Standarized HTTP Client", true);
                
        Thread.sleep(1000);  // waiting for response
        webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "disconnect").thenRun(() -> System.out.println("DISCONNECTED"));
    }

    private static class WebSocketListener implements Listener {

        @Override
        public void onOpen(WebSocket webSocket) {
            System.out.println("CONNECTED");
            Listener.super.onOpen(webSocket);      
        }

        @Override
        public void onError(WebSocket webSocket, Throwable error) {
            System.out.println("Error occurred" + error.getMessage());
            Listener.super.onError(webSocket, error);
        }

        @Override
        public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
            System.out.println("onText: " + data);
            return Listener.super.onText(webSocket, data, last);
        }
        
        @Override
        public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
            System.out.println("onPong: " + new String(message.array()));
            return Listener.super.onPong(webSocket, message);
        }
    }
}
                    

And the output:

CONNECTED onPong: Ping from Dariawan onText: Rock it with HTML5 WebSocket onText: Send text against the echo server onText: Using Java 11 Standarized HTTP Client DISCONNECTED