103 Early Hints (async)
HttpClient 5.6 adds async support for 103 Early Hints via a callback interface:
EarlyHintsListener– functional interface invoked for each103response.HttpAsyncClientBuilder#setEarlyHintsListener(...)– registers a global listener on the async client.EarlyHintsAsyncExec– internal exec-chain handler that taps informational responses without changing final response processing.
This allows applications to observe Early Hints (for example, Link headers
with rel=preload / rel=preconnect) while leaving the normal response path
untouched.
When is the listener called?
- Called once per
103informational response. - Never called for the final non-1xx response.
- May be called multiple times for a single request if the server sends
multiple
103responses. - Runs on the I/O thread, so implementations must be fast and non-blocking; offload heavier work to your own executor.
API surface
The public entry point is the listener:
@FunctionalInterface
public interface EarlyHintsListener {
void onEarlyHints(HttpResponse hints, HttpContext context)
throws HttpException, IOException;
}
You register it on the async client builder:
final EarlyHintsListener listener = (hints, ctx) -> {
// Only 103s will be forwarded here
System.out.println("Early Hints: " + hints.getCode());
for (final Header h : hints.getHeaders("Link")) {
System.out.println(" Link: " + h.getValue());
}
};
final CloseableHttpAsyncClient client = HttpAsyncClients.custom()
.setEarlyHintsListener(listener)
.build();
If the listener is null no extra handler is added to the exec chain.
Minimal usage example
This is a cut-down version of the AsyncClientEarlyHintsEndToEnd example. It
shows how to register the listener and consume the final response as usual.
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.apache.hc.client5.http.EarlyHintsListener;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.http.Header;
public class AsyncClientEarlyHintsExample {
public static void main(final String[] args) throws Exception {
final EarlyHintsListener hintsListener = (hints, ctx) -> {
System.out.println("[client] 103 Early Hints:");
for (final Header h : hints.getHeaders("Link")) {
System.out.println(" " + h.getValue());
}
};
try (final CloseableHttpAsyncClient client = HttpAsyncClients.custom()
.setEarlyHintsListener(hintsListener)
.build()) {
client.start();
final SimpleHttpRequest req = SimpleRequestBuilder
.get("https://example.com/with-early-hints")
.build();
final Future<SimpleHttpResponse> f = client.execute(req, null);
final SimpleHttpResponse resp = f.get(10, TimeUnit.SECONDS);
System.out.println("[client] final: " + resp.getCode() + " " + resp.getReasonPhrase());
System.out.println("[client] body: " + resp.getBodyText());
}
}
}
Notes:
- The final response (e.g.
200 OK) is handled by the normal async flow. - Early Hints are purely side-band notifications.
End-to-end example
The full end-to-end demo lives in the examples package:
This example:
- Starts a local async HTTP server that:
- sends one
103 Early Hintswith twoLinkheaders, then - completes the exchange with
200 OKand a short body.
- sends one
- Builds an async client with
setEarlyHintsListener(...). - Prints:
- all
Linkheaders from103responses, and - the final status and body for the main response.
- all
This is the canonical reference for wiring Early Hints support end-to-end.
Interaction with the exec chain
When setEarlyHintsListener(...) is used, the builder inserts an internal
EarlyHintsAsyncExec handler before the protocol handler:
if (earlyHintsListener != null) {
addExecInterceptorBefore(
ChainElement.PROTOCOL.name(),
"early-hints",
new EarlyHintsAsyncExec(earlyHintsListener));
}
This handler:
- intercepts
handleInformationResponse(...)callbacks, - forwards each
103to the listener, - delegates all other informational responses and the final response unchanged.
If you never register a listener, no extra handler is added and the async exec chain behaves as before.
Summary
- Use
EarlyHintsListener+setEarlyHintsListener(...)to observe103responses. - Normal response processing is unaffected; Early Hints are side-band.
- Keep listener logic lightweight; bounce heavy work to your own executor.
- Use
AsyncClientEarlyHintsEndToEndas a reference for testing and wiring.




