Http Client API in Java: The basics
The steps are straightforward. First, an instance of HttpClient is created, then the HTTP request to be dispatched. Finally, the request is passed to the HttpClient send methods and a response object is returned (or CompletableFuture if the call is asynchronous).
Use Cases In Action
Without any further delay, let’s take a look at some examples:
For this demo, a SpringBoot REST application will be exposing an endpoint (located in http://localhost:8080/api/v1/customers)
that allows to list/add/update/delete customers. Customer is just an ummutable POJO class with a few members. With the help of HttpClient API, we will perform CRUD operations while interacting with the service.
1. Get List of customers
The first scenario is to get a list of all customers. This is just a GET request to the customers resource URL.
HttpClient client = HttpClient .newBuilder() .connectTimeout(Duration.ofMillis(500)) .build();
Note that connection will time out if it is not established in half a second. Next the http request object.
HttpRequests request = HttpRequest .newBuilder() .uri(URI.create("http://localhost:8080/api/v1/customers")) .header("Content-Type", "application/json") .GET() .build();
Now the communication can be done synchronous, that is, execution is blocked until the response is received.
HttpResponseString> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.printf("Status %s \n", response.statusCode()); System.out.printf("Response %s \n", response.body());
The BodyHandlers class contains convenient methods to convert the response body data into java objects like a String.
We could send the same request asynchronously invoking the sendAsynch method. This call is non-blocking and it will
return immediately a CompletableFuture.
CompletableFutureHttpResponseString>> responseFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()); responseFuture .thenApply(HttpResponse::body) .thenApply(String::toUpperCase) .thenAccept(System.out::println) .join();
In the above pipeline, the body is extracted from the response, uppercased and printed.
2. Create a new Customer
POST method will be used to create a new customer. The body must be populated with the customer data in JSON format. The BodyPublishers class provides handy methods to convert from java objects into a flow of data for sending as a request body.
HttpRequest request = HttpRequest .newBuilder() .uri(URI.create("http://localhost:8080/api/v1/customers")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString("")) .build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.printf("Status %s \n", response.statusCode()); System.out.printf("Location %s \n", response.headers().map().get("location"));
Status 201 Location [http://localhost:8080/api/v1/customers/6]
3. Update a new Customer
PUT method will be used to replace entirely an existing customer. That means all fields will be changed except the id. For partial updates, like updating only the email, the PATCH method is more appropiate.
HttpRequest request = HttpRequest .newBuilder() .uri(URI.create("http://localhost:8080/api/v1/customers/4")) .header("Content-Type", "application/json") .PUT(HttpRequest.BodyPublishers.ofString("")) .build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.printf("Status %s \n", response.statusCode()); System.out.printf("Body %s \n", response.body());
Class HttpClient
An HttpClient can be used to send requests and retrieve their responses. An HttpClient is created through a builder . The newBuilder method returns a builder that creates instances of the default HttpClient implementation. 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.
An HttpClient provides configuration information, and resource sharing, for all requests sent through it.
A BodyHandler must be supplied for each HttpRequest sent. The BodyHandler determines how to handle the response body, if any. Once an HttpResponse is received, the headers, response code, and body (typically) are available. Whether the response body bytes have been read or not depends on the type, T , of the response body.
- 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. The CompletableFuture completes when the response becomes available. The returned CompletableFuture can be combined in different ways to declare dependencies among several asynchronous tasks.
HttpClient client = HttpClient.newBuilder() .version(Version.HTTP_1_1) .followRedirects(Redirect.NORMAL) .connectTimeout(Duration.ofSeconds(20)) .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80))) .authenticator(Authenticator.getDefault()) .build(); HttpResponse response = client.send(request, BodyHandlers.ofString()); System.out.println(response.statusCode()); System.out.println(response.body());
HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://foo.com/")) .timeout(Duration.ofMinutes(2)) .header("Content-Type", "application/json") .POST(BodyPublishers.ofFile(Paths.get("file.json"))) .build(); client.sendAsync(request, BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println);
If a security manager is present then security checks are performed by the HTTP Client’s sending methods. An appropriate URLPermission is required to access the destination server, and proxy server if one has been configured. The form of the URLPermission required to access a proxy has a method parameter of «CONNECT» (for all kinds of proxying) and a URL string of the form «socket://host:port» where host and port specify the proxy’s address.
Http client api in java
The HTTP Client was added in Java 11. 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.
Example: GET request that prints the response body as a String
HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://openjdk.org/")) .build(); client.sendAsync(request, BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println) .join();
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.org/")) .timeout(Duration.ofMinutes(1)) .header("Content-Type", "application/json") .POST(BodyPublishers.ofFile(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 response = client.send(request, BodyHandlers.ofString()); 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 composable asynchronous programming.
client.sendAsync(request, BodyHandlers.ofString()) .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 < . >> public abstract class HttpResponse < . public interface BodyHandler < BodySubscriberapply(int statusCode, HttpHeaders responseHeaders); > public interface BodySubscriber extends Flow.Subscriber < . >>
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.BodyPublishers::ofByteArray(byte[]) HttpRequest.BodyPublishers::ofByteArrays(Iterable) HttpRequest.BodyPublishers::ofFile(Path) HttpRequest.BodyPublishers::ofString(String) HttpRequest.BodyPublishers::ofInputStream(Supplier) HttpResponse.BodyHandlers::ofByteArray() HttpResponse.BodyHandlers::ofString() HttpResponse.BodyHandlers::ofFile(Path) HttpResponse.BodyHandlers::discarding()
Thre are adapters between java.util.concurrent.Flow ‘s Publisher / Subscriber types to the HTTP Client’s BodyPublisher / BodySubscriber types:
HttpRequest.BodyPublishers::fromPublisher(. ) HttpResponse.BodyHandlers::fromSubscriber(. ) HttpResponse.BodyHandlers::fromLineSubscriber(. )
HTTP/2
The Java 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 Java HTTP Client is well positioned for the future, when HTTP/2 is more widely deployed.