Backend Development 11 min read

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.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
Master Java 11 HttpClient: Synchronous & Asynchronous Requests Explained

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

HttpTimeoutException

is 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

InputStream

can 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

InputStream

implementation 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‑http

library (based on OkHttp). Example of a

mica‑http

request:

<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.

backendJavaasynchronousHTTPHttpClientJava11synchronous
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.