Since Java 11, you can use HttpClient
API to execute non-blocking HTTP requests and handle responses through CompletableFuture
, which can be chained to trigger dependant actions
The following example sends an HTTP GET request and retrieves its response asynchronously with HttpClient
and CompletableFuture
@Test
public void getAsync() {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8081/test/resource"))
.header("Accept", "application/json")
.build();
int statusCode = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApplyAsync(HttpResponse::statusCode)
.join();
assertThat(statusCode).isEqualTo(200);
}
Let's walk through this tutorial to explore in more details
What you'll need
- JDK 11+
- Maven 3+
- Your favorite IDE
Tech stack
- Java 11+ for learning HttpClient API
- JUnit 4 for writing test cases
- WireMock for mocking Http server
- AssertJ for verifying test result
Create a new HttpClient
You can use HttpClient.newBuilder()
to create a new HttpClient
instance and configure options through fluent APIs
The below example gives you full HttpClient
configuration options
@Test
public void createAnHTTPClient() throws NoSuchAlgorithmException {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.proxy(ProxySelector.getDefault())
.followRedirects(HttpClient.Redirect.NEVER)
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("user", "pass".toCharArray());
}
})
.cookieHandler(new CookieManager())
.executor(Executors.newFixedThreadPool(2))
.priority(1)
.sslContext(SSLContext.getDefault())
.sslParameters(new SSLParameters())
.connectTimeout(Duration.ofSeconds(1))
.build();
assertThat(client.connectTimeout()).get().isEqualTo(Duration.ofSeconds(1));
}
version(HttpClient.Version.HTTP_2))
preferred HTTP version 2, fall back to version 1.1 if the server is not supportedfollowRedirects(HttpClient.Redirect.NEVER)
never redirect an HTTP request. This is the default option, other options areRedirect.ALWAYS
(always redirect), andRedirect.NORMAL
(always redirect except from HTTPS URL to HTTP URLS)authenticator(Authenticator)
set a non-preemptive Basic AuthenticationcookieHandler(new CookieManager())
enables a cookie handler, disabled if this option is ignoredconnectTimeout(Duration.ofSeconds(1))
sets the connect timeout duration for anHttpClient
instance.HttpConnectTimeoutException
will be thrown if the connection cannot be established within the given duration
You can also use HttpClient.newHttpClient()
to create a new HttpClient
with default settings. It is a short form of HttpClient.newBuilder().build()
@Test
public void createADefaultHTTPClient() {
HttpClient client = HttpClient.newHttpClient();
assertThat(client.version()).isEqualTo(HttpClient.Version.HTTP_2);
assertThat(client.followRedirects()).isEqualTo(HttpClient.Redirect.NEVER);
assertThat(client.proxy()).isEmpty();
assertThat(client.connectTimeout()).isEmpty();
assertThat(client.cookieHandler()).isEmpty();
assertThat(client.authenticator()).isEmpty();
assertThat(client.executor()).isEmpty();
}
The created HttpClient
is immutable, so thread-safe, and can be used to send multiple requests
Build a new HttpRequest
You can build a new HttpRequest
with HttpRequest.newBuilder()
@Test
public void buildAnHTTPRequest() {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8081/test/resource"))
.POST(HttpRequest.BodyPublishers.noBody())
.version(HttpClient.Version.HTTP_2)
.header("Accept", "application/json")
.timeout(Duration.ofMillis(500))
.build();
assertThat(request.timeout().get()).isEqualTo(Duration.ofMillis(500));
}
uri(java.net.URI)
sets the URI of the sending requestThe HTTP request method is set by using
GET()
,POST(BodyPublisher)
,PUT(BodyPublisher)
,DELETE(BodyPublisher)
ormethod(String, BodyPublisher)
GET()
is the default request methodversion(HttpClient.Version)
sets the prefered HTTP version for the executing request. The actual using version should be checked in the correspondingHttpResponse
If this option is ignored, the version in the sending HttpClient will be applied
header(String name, String value)
adds the given name-value pair to the set of headers for the executing requestIllegalArgumentException
will be thrown if the header name or value is not validtimeout(Duration)
sets a read timeout for this requestHttpTimeoutException
will be thrown if the response is not received within the specified timeoutIf this option is ignored, the read timeout will be infinite
Send an HttpRequest Synchronously
Use send()
method of an HttpClient
instance to execute an HttpRequest
synchronously
@Test
public void getSync() throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8081/test/resource"))
.header("Accept", "application/json")
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertThat(response.statusCode()).isEqualTo(200);
}
The above example sends a GET HTTP request synchronously with send()
send()
blocks the current thread if necessary to get the response. The returned HttpResponse<T>
contains the response status, headers, and body
Send an HttpRequest Asynchronously
Use sendAsync()
method of an HttpClient
instance to execute an HttpRequest
asynchronously
@Test
public void postAsync() {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8081/test/resource"))
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("ping!"))
.build();
CompletableFuture<HttpResponse<String>> completableFuture =
client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
completableFuture
.thenApplyAsync(HttpResponse::headers)
.thenAcceptAsync(System.out::println);
HttpResponse<String> response = completableFuture.join();
assertThat(response.statusCode()).isEqualTo(200);
}
The above example sends a POST request asynchronously with sendAsync
sendAsync
doesn't block the current thread like send
, it returns a CompletableFuture
immediately. If completed successfully, it completes with an HttpResponse
that contains status, headers, and body
Query Parameters
HttpClient doesn't come with a URI components builder. However, you can add query string parameters to the URL while creating a new URI
URI.create("http://localhost:8081/test/resource?a=b")
Post JSON
There's no built-in JSON support. You can use Jackson or Gson to parse Object to String and vice versa
The following example sends an HTTP POST request through HttpClient with JSON data which is serialized and deserialized by Jackson ObjectMapper
@Test
public void postJson() throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
Book book = new Book(1, "Java HttpClient in practice");
String body = objectMapper.writeValueAsString(book);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8081/test/resource"))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertThat(response.statusCode()).isEqualTo(200);
assertThat(objectMapper.readValue(response.body(), Book.class).id).isEqualTo(1);
}
Basic Authentication
Add basic authentication to HttpClient with the following approaches
- Use
authenticator()
method of anHttpClient
instance
HttpClient client = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("user", "pass".toCharArray());
}
})
.build();
- Add
Authorization
header to anHttpRequest
instance. Use this option when a preemptive basic authentication is required by a server like WireMock
@Test
public void basicAuthentication() throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
String encodedAuth = Base64.getEncoder()
.encodeToString(("user" + ":" + "pass").getBytes(StandardCharsets.UTF_8));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8081/test/secure"))
.header("Authorization", "Basic " + encodedAuth)
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertThat(response.statusCode()).isEqualTo(200);
}
Cookie Handler
Cookies are disabled by default. To enable, create a new CookieManager
and add it to cookieHandler
method of an HttpClient
HttpClient client = HttpClient.newBuilder()
.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ALL))
.build();
There're 3 cookie policies, namely ACCEPT_ALL, ACCEPT_NONE, and ACCEPT_ORIGINAL_SERVER (default)
@Test
public void cookie() throws IOException, InterruptedException {
HttpClient client = HttpClient.newBuilder()
.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ALL))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8081/test/set-cookie"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String cookie = response.headers().firstValue("Set-Cookie").get();
assertThat(HttpCookie.parse(cookie).get(0).getName()).isEqualTo("SID");
request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8081/test/resource"))
.header("Accept", "application/json")
.build();
response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertThat(response.statusCode()).isEqualTo(200);
}
Conclusion
In this tutorial, we learned how to create a new HttpClient
instance, configure and use it to build and execute an HTTP request synchronously and asynchronously. You can find the full source code as below