JDK-8291976 : JEP 517: HTTP/3 for the HTTP Client API
  • Type: JEP
  • Component: core-libs
  • Sub-Component: java.net
  • Priority: P3
  • Status: Candidate
  • Resolution: Unresolved
  • Submitted: 2022-08-05
  • Updated: 2025-05-19
Related Reports
Blocks :  
Blocks :  
Relates :  
Relates :  
Relates :  
Description
Summary
-------

Update the [HTTP Client API] to support the [HTTP/3] protocol, so that libraries and applications can interact with HTTP/3 servers with minimal code change.


Goals
-----

- Update the HTTP Client API to send and receive HTTP/3 requests and responses.

- Require only minor changes to the HTTP Client API and to application code.

- Do not change the default protocol version from HTTP/2 to HTTP/3 but, rather, enable developers to opt-in to HTTP/3.


Non-Goals
---------

- It is not a goal to provide an API for the [QUIC] protocol, upon which [HTTP/3] is based.

- It is not a goal to support [third-party secure-socket providers].

- It is not a goal to provide a server-side implementation of the HTTP/3 protocol.

- It is not a goal to update the protocol handler used by the legacy [java.net.URL] API, which only supports HTTP/1.1.


Motivation
----------

[JEP 321] (JDK 11) added a modern [HTTP Client API] to the Java Platform. The API supports HTTP/1.1 and HTTP/2, and was designed to support future protocol versions with minimal change. The API prefers HTTP/2 by default, but transparently downgrades to HTTP/1.1 if the target server does not support HTTP/2.

The HTTP Client API makes it easy to write code that interacts with HTTP servers. For example, to send a GET request to `https://openjdk.org/` and receive the response as a string:

    import java.net.http.*;

    ...

    var client = HttpClient.newHttpClient();
    var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/"))
                             .GET().build();
    var response = client.send(request, HttpResponse.BodyHandlers.ofString());
    assert response.statusCode() == 200;
    String htmlText = response.body();

Here there is nothing explicit in the use of the API that depends on the HTTP protocol version. The application code is agnostic to the protocol.

Unfortunately, the HTTP Client API does not support the latest version of the HTTP protocol. HTTP/3 was standardized in 2022 by the [Internet Engineering Task Force] (IETF). HTTP/3 is a successor to HTTP/2 which uses the [QUIC] reliable transport-layer protocol rather than TCP. QUIC is secured with [Transport Layer Security (TLS)] version 1.3.

Supporting HTTP/3 would enable applications using the HTTP Client API to benefit from the many improvements offered by the HTTP/3 protocol, including

- Potentially faster [handshakes],
- Avoidance of network congestion issues such as [head-of-line blocking], and
- More reliable transport, especially in environments with high rates of packet loss.

HTTP/3 is already [supported by most web browsers] and [deployed on about a third of all web sites]. The Java Platform should support it for client-side use.


Description
-----------

To send a request using HTTP/3, you must opt-in to using it.

You can do this by setting the protocol version of an [`HttpClient`] object to HTTP/3, which will cause all requests sent with the client to prefer HTTP/3 by default:

<pre><code>var client = HttpClient.newBuilder()
                       .<b>version(HttpClient.Version.HTTP_3)</b>
                       .build();
</code></pre>

Alternatively, you can set the preferred protocol version of an individual [`HttpRequest`] object:

<pre><code>var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/"))
                         .<b>version(HttpClient.Version.HTTP_3)</b>
                         .GET().build();
</code></pre>

No other changes are needed. After selecting HTTP/3 as the preferred version, either in the request or in the client, you send the request in the usual way. If the target server does not support HTTP/3 then the request will, by default, be transparently downgraded to HTTP/2 or even HTTP/1.1, as appropriate.

### Negotiating HTTP protocol versions

It is impossible to determine, in advance, whether a target server supports HTTP/3. It is also impossible to upgrade an existing HTTP/1.1 or HTTP/2 connection to an HTTP/3 connection, since HTTP/1.1 and HTTP/2 are built on top of TCP streams while HTTP/3's QUIC is built on top of UDP datagrams.

How, then, does the HTTP Client API determine whether it can use HTTP/3 for a given target server? There are four basic approaches:

- Send the first request using HTTP/2 or HTTP/1.1. If the response from the server indicates that HTTP/3 is available as an [alternative service][RFC 7838] then use HTTP/3 for all subsequent requests. (This is what happens when the `HttpClient` object has a preferred version of `HTTP_3` and the `HttpRequest` object does not have a preferred version.)

- Send the first request using HTTP/3. If an HTTP/3 response is not received in a reasonable time then fall back to HTTP/1.1 or HTTP/2. (This is what happens when the `HttpRequest` object has a preferred version of `HTTP_3`.)

- Send the first request twice, once using HTTP/3 and once using HTTP/2 or HTTP/1.1. Use the protocol of the first response received. (This is what happens when you specify [`Http3DiscoveryMode.ANY`] as the value of the [`H3_DISCOVERY`] request option, the `HttpClient` object has a preferred version of `HTTP_3`, and the `HttpRequest` object does not have a preferred version.)

- Send all requests using HTTP/3. If the server does not reply via HTTP/3 then fail; do not fall back to HTTP/1.1 or HTTP/2. (This is what happens when you specify [`Http3DiscoveryMode.HTTP_3_URI_ONLY`] as the value of the [`H3_DISCOVERY`] request option and at least one of the `HttpClient` or `HttpRequest` objects has a preferred version of `HTTP_3`.)

These approaches have tradeoffs: The first requires using HTTP/2 or HTTP/1.1 initially, the second requires waiting for a timeout, the third may load the HTTP/3 implementation but never again use it, and the fourth works only if you know in advance that the target server supports HTTP/3.

Because HTTP/3 is not yet widely deployed, no single approach will work for all circumstances. Therefore we are not proposing to make HTTP/3 the default at this time, though we may do so in the future.

### Further potential enhancements

- Enable limited configuration and tuning of the HTTP/3 implementation, for instance by means of JDK-specific system properties or, possibly, small new APIs.

- Define new subclasses of `IOException` and `SSLException` for HTTP/3-specific exception conditions.

- Enhance the [`PushPromiseHandler`] interface to support HTTP/3's ability to share promise responses across distinct request/response streams within the same connection.

- Add configuration options to the [`HttpClient.Builder`] and [`HttpRequest.Builder`] APIs to control discovery of the target server.


Testing
-------

We will do extensive unit and integration testing. We will also test interoperation with server-side HTTP/3 implementations including [Netty], [quic-go], [quiche], [Neqo], and [nghttp3].


Risks and Assumptions
---------------------

This first implementation of HTTP/3 will not support secure-socket providers other than the default provider, [SunJSSE]. Support for [third-party secure-socket providers] would require adding methods to the provider SPI, and then the maintainers of such providers would have to implement those methods. We may address this in future work.


Dependencies
------------

The QUIC Protocol is defined by:

- [RFC 8999]: Version-Independent Properties of QUIC
- [RFC 9000]: A UDP-Based Multiplexed and Secure Transport
- [RFC 9001]: Using TLS to Secure QUIC
- [RFC 9002]: QUIC Loss Detection and Congestion Control

The HTTP/3 protocol is defined by:

- [RFC 9114]\: HTTP/3
- [RFC 9204]: QPACK: Field Compression for HTTP/3

These RFCs are of interest for discovering QUIC endpoints and measuring [network path MTU]:

- [RFC 7838]: HTTP Alternative Services
- [RFC 8899]: Packetization Layer Path MTU Discovery for Datagram Transports
- [RFC 4821]: Packetization Layer Path MTU Discovery

Two additional RFCs are of interest but may not be supported by the first implementation:

- [RFC 9368]: Compatible Version Negotiation for QUIC
- [RFC 9369]: QUIC Version 2


[HTTP Client API]: https://cr.openjdk.org/~dfuchs/8291976/apidoc.02/api/java.net.http/java/net/http/HttpClient.html
[`HttpClient`]: https://cr.openjdk.org/~dfuchs/8291976/apidoc.02/api/java.net.http/java/net/http/HttpClient.html
[`HttpRequest`]: https://cr.openjdk.org/~dfuchs/8291976/apidoc.02/api/java.net.http/java/net/http/HttpRequest.html
[HTTP/3]: https://en.wikipedia.org/wiki/HTTP/3
[Internet Engineering Task Force]: https://www.ietf.org/
[QUIC]: https://en.wikipedia.org/wiki/QUIC
[java.net.URL]: https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/net/URL.html
[Transport Layer Security (TLS)]: https://en.wikipedia.org/wiki/Transport_Layer_Security
[third-party secure-socket providers]: https://docs.oracle.com/en/java/javase/24/security/java-secure-socket-extension-jsse-reference-guide.html#GUID-B7AB25FA-7F0C-4EFA-A827-813B2CE7FBDC
[SunJSSE]: https://docs.oracle.com/en/java/javase/24/security/oracle-providers.html#GUID-7093246A-31A3-4304-AC5F-5FB6400405E2
[JEP 321]: https://openjdk.org/jeps/321
[handshakes]: https://en.wikipedia.org/wiki/Handshake_(computing)
[head-of-line blocking]: https://en.wikipedia.org/wiki/Head-of-line_blocking
[supported by most web browsers]: https://caniuse.com/http3
[deployed on about a third of all web sites]: https://w3techs.com/technologies/details/ce-http3
[`PushPromiseHandler`]: https://cr.openjdk.org/~dfuchs/8291976/apidoc.02/api/java.net.http/java/net/http/HttpResponse.PushPromiseHandler.html
[`HttpClient.Builder`]: https://cr.openjdk.org/~dfuchs/8291976/apidoc.02/api/java.net.http/java/net/http/HttpClient.Builder.html
[`HttpRequest.Builder`]: https://cr.openjdk.org/~dfuchs/8291976/apidoc.02/api/java.net.http/java/net/http/HttpRequest.Builder.html
[`HttpOption`]: https://cr.openjdk.org/~dfuchs/8291976/apidoc.02/api/java.net.http/java/net/http/HttpOption.html

[`Http3DiscoveryMode.HTTP_3_URI_ONLY`]: https://cr.openjdk.org/~dfuchs/8291976/apidoc.02/api/java.net.http/java/net/http/HttpOption.Http3DiscoveryMode.html#HTTP_3_URI_ONLY
[`Http3DiscoveryMode.ANY`]: https://cr.openjdk.org/~dfuchs/8291976/apidoc.02/api/java.net.http/java/net/http/HttpOption.Http3DiscoveryMode.html#ANY
[`H3_DISCOVERY`]: https://cr.openjdk.org/~dfuchs/8291976/apidoc.02/api/java.net.http/java/net/http/HttpOption.html#H3_DISCOVERY

[Netty]: https://netty.io/
[quic-go]: https://github.com/quic-go/quic-go
[quiche]: https://github.com/cloudflare/quiche
[Neqo]: https://github.com/mozilla/neqo
[nghttp3]: https://github.com/ngtcp2/nghttp3

[RFC 7838]: https://www.rfc-editor.org/rfc/rfc7838
[RFC 8999]: https://www.rfc-editor.org/rfc/rfc8999
[RFC 9000]: https://www.rfc-editor.org/rfc/rfc9000
[RFC 9001]: https://www.rfc-editor.org/rfc/rfc9001
[RFC 9002]: https://www.rfc-editor.org/rfc/rfc9002
[RFC 9114]: https://www.rfc-editor.org/rfc/rfc9114
[RFC 9204]: https://www.rfc-editor.org/rfc/rfc9204
[RFC 8899]: https://www.rfc-editor.org/rfc/rfc8899
[RFC 4821]: https://www.rfc-editor.org/rfc/rfc4821
[RFC 9368]: https://www.rfc-editor.org/rfc/rfc9368
[RFC 9369]: https://www.rfc-editor.org/rfc/rfc9369

[network path MTU]: https://en.wikipedia.org/wiki/Path_MTU_Discovery

Comments
That the `[RFC 9114]: HTTP/3` item isn’t rendered appears to be a consequence of the CommonMark specification. The spec treats that text as a link reference definition, even though it’s inside an unordered list. A workaround is to quote the colon: `[RFC 9114]\: HTTP/3`. The item is rendered in JBS because the JBS Markdown renderer is broken.
02-12-2024