Java 26 HTTP/3: Configure HttpClient Discovery and Fallback
Enable HTTP/3 in Java 26 HttpClient, choose a discovery mode, verify the negotiated response version, and handle fallback, HTTPS, and proxy limits.
JDK 26 adds HTTP/3 to Java’s built-in HttpClient, but selecting HTTP_3 expresses a preference rather than guaranteeing the protocol. With the normal discovery mode, a request can still complete over HTTP/2 or HTTP/1.1. Configure the preference, choose how the client discovers a QUIC endpoint, and check HttpResponse.version() when the negotiated protocol matters.
This API is final in JDK 26 and does not exist in JDK 25. You need a JDK 26 compiler and runtime for HttpClient.Version.HTTP_3, HttpOption, and the HTTP/3 discovery modes below. A class-path application needs no extra dependency; a named module must already requires java.net.http as it would for earlier versions of HttpClient.
Prefer HTTP/3 for one request
The smallest useful probe sets the preference on a request and prints the version of the response:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import static java.net.http.HttpClient.Version.HTTP_3;
public final class Http3Probe {
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("usage: java Http3Probe <https-url>");
System.exit(2);
}
var request = HttpRequest.newBuilder(URI.create(args[0]))
.version(HTTP_3)
.timeout(Duration.ofSeconds(20))
.HEAD()
.build();
try (var client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build()) {
var response = client.send(
request,
HttpResponse.BodyHandlers.discarding());
System.out.printf("status=%d version=%s%n",
response.statusCode(), response.version());
}
}
}
Compile and run it with JDK 26, supplying any HTTPS endpoint. Use an endpoint you control or know supports HTTP/3 when testing successful negotiation; a server without HTTP/3 still demonstrates fallback:
javac --release 26 Http3Probe.java
java Http3Probe https://example.com/
Do not infer the protocol from a successful response. The endpoint, network path, and discovery result can cause fallback. The JDK 26 HttpRequest.Builder.version contract explicitly calls the configured value a preferred version and directs callers to inspect the response. HttpResponse.version() returns the version actually used.
Set .version(HTTP_3) on HttpClient.Builder instead when HTTP/3 should be the default preference for every request sent by that client:
var client = HttpClient.newBuilder()
.version(HTTP_3)
.build();
A request-level version takes precedence for that request. This lets an application use one HTTP/3-preferring client while selecting HTTP/2 for a destination that needs it. A newly built client otherwise continues to prefer HTTP/2; JEP 517 deliberately made HTTP/3 an opt-in feature in JDK 26.
Choose how the client discovers HTTP/3
HTTP/3 carries HTTP semantics over QUIC rather than over the TCP connections used by HTTP/1.1 and HTTP/2. An existing TCP connection therefore cannot simply be upgraded to HTTP/3. The request option HttpOption.H3_DISCOVERY tells the JDK client how to find or establish a QUIC connection.
The built-in implementation supports three Http3DiscoveryMode values:
-
ANYlets the implementation choose. It may attempt a direct QUIC connection and a TLS/TCP connection, then use the connection that succeeds first. It may also use HTTP Alternative Services information learned earlier. The response can use HTTP/1.1, HTTP/2, or HTTP/3. -
ALT_SVCpermits HTTP/3 discovery only through an HTTP Alternative Services advertisement. Initial requests use HTTP/1.1 or HTTP/2 until the client receives anh3alternative; a later request made with the same long-lived client can then use that endpoint. -
HTTP_3_URI_ONLYattempts only HTTP/3 at the exact host and port in the request URI. It ignores alternative services and does not fall back. Use it for a strict capability check or when failure is preferable to silently using an older protocol.
If no discovery option is present while HTTP/3 is preferred, the JDK’s built-in client uses ANY. An explicit option is useful when the intended failure behavior should be visible in code:
import static java.net.http.HttpOption.H3_DISCOVERY;
import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY;
var strictRequest = HttpRequest.newBuilder(uri)
.version(HTTP_3)
.setOption(H3_DISCOVERY, HTTP_3_URI_ONLY)
.timeout(Duration.ofSeconds(10))
.GET()
.build();
H3_DISCOVERY has no effect unless either the client or request prefers HTTP/3. It is a configuration hint in the general API: the JDK’s built-in HttpClient understands it, but another HttpClient implementation is allowed to ignore an option it does not understand or fail the request. The JDK 26 HttpOption contract defines both that scope and the built-in implementation’s behavior.
Fallback is part of normal operation
ANY is usually the practical production choice because UDP can be unavailable, a server may not expose HTTP/3, or the TCP connection may win the connection race. Those outcomes do not make the request unsuccessful: the client can complete it with HTTP/2 or HTTP/1.1. Treat response.version() == HTTP_3 as an observed result, not a precondition, unless the application genuinely requires HTTP/3.
Use strict mode in an integration check when the endpoint contract requires it:
var response = client.send(
strictRequest,
HttpResponse.BodyHandlers.discarding());
if (response.version() != HTTP_3) {
throw new IllegalStateException(
"expected HTTP/3, got " + response.version());
}
In HTTP_3_URI_ONLY mode, inability to establish the direct QUIC connection fails the send instead of producing a lower-version response. Keep both the connect and request timeouts finite so a blocked UDP path does not turn a capability check into an unbounded wait.
This is a transport choice, not a reason to duplicate application retries. Decide retry safety from the HTTP method and application semantics, just as with other connection failures.
HTTPS and proxies constrain the result
The JDK 26 client never sends a URI whose scheme is not https over HTTP/3. Asking for HTTP_3 on an http URI can therefore only produce a lower protocol version or a failure under stricter constraints. Validate command-line or configuration input before treating an HTTP/3 check as meaningful.
The built-in implementation also does not support HTTP/3 through a selected proxy. With ANY or ALT_SVC, it downgrades to HTTP/2 or HTTP/1.1 and ignores the discovery hint. With HTTP_3_URI_ONLY, the request fails instead. These rules are part of the JDK 26 protocol-selection documentation and apply whether the proxy was configured explicitly or selected for the request URI.
A custom SSLContext or SSLParameters can also be incompatible with HTTP/3. In that case client construction can fail with UncheckedIOException; a strict HTTP/3 send can throw UnsupportedProtocolVersionException. Test the same TLS and proxy configuration used in deployment rather than relying on a probe made with client defaults.
Redirects deserve the same scrutiny. When redirects are enabled, the built-in client transfers H3_DISCOVERY to the redirected request, but the new URI still has its own scheme, authority, proxy selection, and protocol outcome. Inspect both response.uri() and response.version() if the final destination matters.
Match the mode to the requirement
Use request-level HTTP_3 with the default ANY mode when you want to adopt HTTP/3 without sacrificing reachability. Use ALT_SVC when the origin’s advertisement should authorize the alternative QUIC endpoint and an initial TCP exchange is acceptable. Use HTTP_3_URI_ONLY when testing direct support at the URI authority and a failure is the desired signal.
Whichever mode you choose, reuse HttpClient instances so connections and learned alternative-service information can benefit later requests. Keep HTTP/3-specific decisions at the transport boundary; status handling, bodies, authentication, and application semantics continue to use the same HttpRequest and HttpResponse APIs.
The relevant wire protocol is standardized in RFC 9114, while Java’s opt-in, fallback, and discovery behavior is the JDK 26 API contract. That division matters: the RFC explains what HTTP/3 is, but the Java documentation determines what this client does when HTTP/3 is preferred and unavailable.