1. Introduction
The HTTP Client was added in Java 11. This class was introduced to replace the legacy HttpUrlConnection
class. Due to the issues with HttpUrlConnection
, programmers used third-party libraries such as Apache HttpClient and Jetty.
HttpClient
is an abstract class available in java.net.http
module and java.net.http
package. This class represents an HTTP Client. An HttpClient
can be used to send requests and retrieve their responses.
HttpClient
can be used to request HTTP resources over the network. HttpClient
supports both HTTP/1.1 and HTTP/2. HttpClient
can be used to make both synchronous and asynchronous calls. It can also handle request and response bodies as reactive-streams.
Once created HttpClient
is immutable, and can be used to send multiple requests.
The Java HTTP Client supports both HTTP/1.1 and HTTP/2. By default, when making requests, the client will utilize HTTP/2. If the server does not support HTTP/2, the requests will automatically revert to HTTP/1.1.
Here’s an overview of the key enhancements introduced by HTTP/2:
- Header Compression: HTTP/2 employs HPACK compression, minimizing overhead.
- Single Connection to the server: Reduces the necessity for numerous TCP connections to be established, thereby cutting down on round trips.
- Multiplexing: Allows multiple requests concurrently on a single connection.
- Server Push: Permits the server to proactively send additional required resources to the client.
- Binary format: Offers a more concise representation.
Following are some important points to note about this change:
- The change was implemented as a part of JEP 321. The new HTTP API was incubated in Java 9. This new API in now included in Java SE API. The new HTTP APIs can be found in
java.net.HTTP.*
. - The new HTTP API now supports asynchronous. Asynchronous calls are implemented using
CompletableFuture
. - The core classes and interface providing the core functionality include:
TheHttpClient
class,java.net.http.HttpClient
TheHttpRequest
class,java.net.http.HttpRequest
TheHttpResponse
interface,java.net.http.HttpResponse
TheWebSocket
interface,java.net.http.WebSocket
A BodyHandler
must be provided for each HttpRequest
sent. A BodyHandler
is a handler for response bodies.
Requests can be sent either synchronously or asynchronously. There are two methods to send request synchronously and asynchronously:
send(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler)
: Sends the given request using the client, blocking if necessary to get the response.sendAsync(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler)
: Sends the given request asynchronously using the client with the given response body handler. ThesendAsync
method returns immediately with aCompletableFuture
. TheCompletableFuture
completes when the response becomes available.
Security checks are performed by the HTTP Client’s sending methods if a security manager is present.
Before sending a request, HttpClient
need to be created. HttpClient
supports the builder pattern. An HttpClient
is created through a builder. The Builder can be used to configure state of client like connection timeout, preferred protocol version (HTTP/1.1 OR HTTP2), authenticator, proxy, follow redirects etc.
HttpClient client = HttpClient.newBuilder() .version(Version.HTTP_2) .followRedirects(Redirect.NORMAL) .proxy(ProxySelector.of(new InetSocketAddress("www.learnitweb.com", 8080))) .authenticator(Authenticator.getDefault()) .build();
2. HttpRequest
An HttpRequest
instance is built through an HttpRequest
builder. HttpRequest
can be used to set a request’s URI, headers, and body. Builders can be reused by copying and modifying many times when required in order to build multiple related requests that differ in some parameters.
HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://learnitweb.com/")) .timeout(Duration.ofMinutes(1)) .header("Content-Type", "application/json") .POST(BodyPublishers.ofFile(Paths.get("file.json"))) .build()
3. Setting Executor for Asynchronous Calls
We can also define an Executor that provides threads to be used by asynchronous calls.
This way we can, for example, set the limit on the number of threads used for processing requests:
ExecutorService executorService = Executors.newFixedThreadPool(2); CompletableFuture<HttpResponse<String>> response1 = HttpClient.newBuilder() .executor(executorService) .build() .sendAsync(request, HttpResponse.BodyHandlers.ofString()); CompletableFuture<HttpResponse<String>> response2 = HttpClient.newBuilder() .executor(executorService) .build() .sendAsync(request, HttpResponse.BodyHandlers.ofString());
4. Setting a CookieHandler
We can use builder method cookieHandler(CookieHandler cookieHandler)
to define client-specific CookieHandler
.
HttpClient.newBuilder() .cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_NONE)) .build();
5. Setting a Proxy
We can define a proxy for the connection by just calling proxy()
method on a Builder instance:
HttpResponse<String> response = HttpClient .newBuilder() .proxy(ProxySelector.getDefault()) .build() .send(request, BodyHandlers.ofString());
In this example, we used the default system proxy.
6. Setting a Request Body
We can add a body to a request by using the request builder methods: POST(BodyPublisher body)
, PUT(BodyPublisher body)
and DELETE()
. The new API provides a number of BodyPublisher
implementations:
- StringProcessor – One of the overloaded method is
ofString(String body)
. This method returns a request body publisher whose body is the given String, converted using the UTF_8 character set. - InputStreamProcessor – One of the overloaded method is
ofInputStream(Supplier streamSupplier)
– A request body publisher that reads its data from anInputStream
. - ByteArrayProcessor – One of the overloaded method is
ofByteArray(byte[] buf)
. This method returns a request body publisher whose body is the given byte array. - FileProcessor – reads body from a file at the given path, created with HttpRequest.BodyPublishers.ofFile.
HttpRequest request = HttpRequest.newBuilder() .uri(new URI("https://someapi-endpoint")) .POST(HttpRequest.BodyPublishers.noBody()) .build();
7. Setting headers
In case we want to add additional headers to our request, we can use the provided builder methods. Headers are passed as key-value pairs.
HttpRequest request = HttpRequest.newBuilder() .uri(new URI("https://some-endpoint")) .headers("key1", "value1", "key2", "value2") .GET() .build();
8. Synchronous API call example
Following is an example of an API call using HttpClient.
import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; public class HttpClientExample { public static void main(String args[]) { //Create an instance of HttpClient HttpClient httpClient = HttpClient.newHttpClient(); // Define the URL you want to send the GET request to String url = "https://jsonplaceholder.typicode.com/todos/1"; // Create a HttpRequest object for the GET request HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .GET() .build(); try { // Send the GET request synchronously and capture the response HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); // Print the response status code System.out.println("Response Status Code: " + response.statusCode()); // Print the response body System.out.println("Response Body: " + response.body()); } catch (Exception e) { // Handle any exceptions e.printStackTrace(); } } }
Output
Response Status Code: 200
Response Body: {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
9. Asynchronous call
Let us modify the earlier example for asynchronous call.
import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.concurrent.CompletableFuture; public class AsyncHttpClientExample { public static void main(String[] args) { // Create an instance of HttpClient HttpClient httpClient = HttpClient.newHttpClient(); // Define the URL you want to send the GET request to String url = "https://jsonplaceholder.typicode.com/todos/1"; // Create a HttpRequest object for the GET request HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .GET() .build(); // Send the GET request asynchronously CompletableFuture<HttpResponse<String>> futureResponse = httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()); // Handle the response when it becomes available futureResponse.thenAccept(response -> { // Print the response status code System.out.println("Response Status Code: " + response.statusCode()); // Print the response body System.out.println("Response Body: " + response.body()); }).join(); // Wait for the asynchronous operation to complete } }
We use httpClient.sendAsync()
to send the GET request asynchronously. This method returns a CompletableFuture<HttpResponse<String>>, which represents a future completion of the HTTP request.
We use futureResponse.thenAccept()
to specify a callback function to handle the response when it becomes available. Inside the callback, we print the response status code and body.
10. Get Response details
We’ll update the code to get the response code, response headers, response version and URI of response object. Other part of the program in omitted for brevity.
// Handle the response when it becomes available futureResponse.thenAccept(response -> { // Print the response status code System.out.println("Response Status Code: " + response.statusCode()); // Print the response headers System.out.println("Response Headers:"); Map<String, List<String>> headers = response.headers().map(); headers.forEach((name, value) -> System.out.println(name + ": " + value)); // Print the response version System.out.println("Response Version: " + response.version()); // Print the URI of the response object System.out.println("Response URI: " + response.uri()); // Print the response body System.out.println("Response Body: " + response.body()); }).join(); // Wait for the asynchronous operation to complete
11. Conclusion
In conclusion, Java 11’s HttpClient offers a powerful and versatile solution for handling HTTP requests and responses in your applications. Throughout this tutorial, we’ve delved into its various features, from simple API calls to more advanced functionalities like asynchronous execution and handling of redirects.
Furthermore, HttpClient’s support for modern protocols and standards, such as HTTP/2 and WebSocket, ensures compatibility with the evolving landscape of web technologies. Its flexibility in configuring connection management, timeouts, and proxy settings enables developers to tailor their network interactions according to specific requirements.