Master Java 11 HttpClient: Synchronous & Asynchronous Requests Explained
This article introduces Java 11's built-in HttpClient, compares it with legacy HttpURLConnection and third-party libraries, and provides detailed code examples for creating and configuring HttpRequest objects, sending synchronous and asynchronous requests, handling headers, timeouts, bodies, and demonstrates a custom mica-http wrapper for practical use.
1. Introduction
Before Java 11, Java only provided HttpURLConnection API, which was hard to use and performed poorly, so developers typically used third‑party libraries such as Apache HttpClient, Jetty, OkHttp, and Spring's RestTemplate.
Java 11 introduces a new HTTP Client API that supports both synchronous and asynchronous request mechanisms.
The API centers on three classes: HttpRequest (represents the request to be sent), HttpClient (sends synchronous or asynchronous requests), and HttpResponse (represents the result of the request). The following sections explore them in detail.
2. HttpClient Synchronous and Asynchronous
Example of a synchronous request from the Javadoc:
<code>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();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.dreamlu.net"))
.timeout(Duration.ofMinutes(2))
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofFile(Paths.get("file.json")))
.build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
</code>This code sends the request and blocks until the response is received, then prints the status code and body.
Asynchronous example using the HttpClient API:
<code>HttpClient client = HttpClient.newBuilder()
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.dreamlu.net"))
.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);
</code>The sendAsync() method sends an asynchronous request, while send() sends a synchronous one.
3. HttpRequest Details
1. HttpRequest
HttpRequest represents the request to be sent. Instances are created via
HttpRequest.newBuilder(). In JDK 16 a new overload
HttpRequest.newBuilder(HttpRequest request, BiPredicate filter)copies configuration from an existing request.
2. Setting the URI
Setting a URI is the first step when creating a new request. It can be done via the builder constructor or by calling
uri(URI)on the builder.
<code>HttpRequest.newBuilder(new URI("https://www.dreamlu.net/get"));
HttpRequest.newBuilder()
.uri(new URI("https://www.dreamlu.net/get"));
</code>3. Choosing the HTTP Method
The request method is defined through builder methods such as:
GET()
POST(BodyPublisher body)
PUT(BodyPublisher body)
DELETE()
Example of a simple GET request:
<code>HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://www.dreamlu.net/get"))
.GET()
.build();
</code>4. Setting HTTP Protocol Version
The HttpClient API supports HTTP/2 by default. The version can be set with the builder’s
version()method:
<code>HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://www.dreamlu.net/get"))
.version(HttpClient.Version.HTTP_2)
.GET()
.build();
</code>If HTTP/2 is not supported, the client automatically falls back to HTTP/1.1.
5. Adding Headers
Headers can be added using the builder’s
headers()method or by calling
header()repeatedly:
<code>HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://www.dreamlu.net/get"))
.headers("key1", "value1", "key2", "value2")
.GET()
.build();
HttpRequest request2 = HttpRequest.newBuilder()
.uri(new URI("https://www.dreamlu.net/get"))
.header("key1", "value1")
.header("key2", "value2")
.GET()
.build();
</code>6. Configuring Timeout
A timeout can be set with
timeout(Duration). If the timeout expires, a
HttpTimeoutExceptionis thrown. By default the timeout is effectively infinite.
<code>HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://www.dreamlu.net/get"))
.timeout(Duration.of(10, SECONDS))
.GET()
.build();
</code>7. Setting the Request Body
The body can be added with methods such as
POST(BodyPublisher),
PUT(BodyPublisher), or
DELETE(). If no body is needed,
BodyPublishers.noBody()can be used.
<code>HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://www.dreamlu.net/post"))
.POST(HttpRequest.BodyPublishers.noBody())
.build();
</code>8. Sending a String Body
String bodies can be sent with
BodyPublishers.ofString()or a similar factory method:
<code>HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://www.dreamlu.net/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString("Sample request body"))
.build();
</code>9. Sending an InputStream Body
An
InputStreamcan be supplied via a
Supplier:
<code>byte[] sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://www.dreamlu.net/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofInputStream(() -> new ByteArrayInputStream(sampleData)))
.build();
</code>Any
InputStreamimplementation can be used.
10. Sending a ByteArray
A byte array can be sent with
BodyPublishers.ofByteArray():
<code>byte[] sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://www.dreamlu.net/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofByteArray(sampleData))
.build();
</code>4. Conclusion
In terms of usability, the built‑in Java 11 HttpClient is not as convenient as the author's open‑source
mica‑httplibrary (based on OkHttp). Example of a
mica‑httprequest:
<code>// synchronous, returns null on exception
String html = HttpRequest.get("https://www.baidu.com")
.connectTimeout(Duration.ofSeconds(1000))
.query("test", "a")
.query("name", "張三")
.query("x", 1)
.query("abd", Base64Util.encode("123&$#%"))
.queryEncoded("abc", Base64Util.encode("123&$#%"))
.execute()
.onFailed(((request, e) -> {
e.printStackTrace();
}))
.onSuccess(ResponseSpec::asString);
</code>With the adoption of Spring Boot 3.x, many projects will move to Java 17, making the JDK‑provided HttpClient an excellent choice for SDK and middleware development.
Java Architecture Diary
Committed to sharing original, high‑quality technical articles; no fluff or promotional content.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.