JEP draft: Support HTTP/3 in the HttpClient

OwnerDaniel Fuchs
TypeFeature
ScopeSE
StatusSubmitted
Componentcore-libs / java.net
EffortL
DurationL
Relates toJEP 321: HTTP Client API
Reviewed byAlan Bateman, Bradford Wetmore, Paul Sandoz
Created2022/08/05 14:58
Updated2024/09/06 16:30
Issue8291976

Summary

Update the HTTP Client API to support the HTTP/3 protocol. This will allow applications and libraries to interact with HTTP/3 servers and get the benefits of HTTP/3 with minimal code changes.

Goals

Non-Goals

Motivation

JEP 321 added a modern HTTP client API to the Java Platform in Java 11. The Client API is protocol agnostic and currently supports versions HTTP/1.1 and HTTP/2 of the HTTP protocol. It is designed to support future HTTP protocol versions with minimal API changes. By default, the preferred version used by the HttpClien API is HTTP/2, though it can be transparently downgraded to HTTP/1.1 if the target server doesn't support HTTP/2.

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

import java.net.http.*;
// ...
static HttpClient httpClient = HttpClient.newBuilder().build();
//...
var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/")).GET().build();
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
assert response.statusCode() == 200;
String htmlText = response.body();

In this example 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 an evolution of HTTP/2 based on QUIC (pronounced "quick"), a new transport protocol over the User Datagram Protocol (UDP). Unlike previous versions of HTTP, HTTP/3 doesn't use TCP/IP. The QUIC protocol provides a reliable transport layer secured with the Transport Layer Security (TLS) version 1.3, as specified by QUIC-TLS.

Supporting HTTP/3 will enable applications using the HTTP Client API to benefit from the many improvements offered by the new protocol, such as:

HTTP/3 is already widely adopted by major companies, supported by many browser implementations. The Java Platform should also support it.

Description

To send a request using HTTP/3, you must opt in to HTTP/3 by setting the default version of the HttpClient to HTTP/3 or by explicitly setting the version of the HttpRequest to HTTP/3. In the example shown above, enabling HTTP/3 would simply require selecting HTTP/3 using the new addition to the API. For instance:

Selecting HTTP_3 either as preferred version for the client:

static HttpClient httpClient = HttpClient.newBuilder()
                           .version(HttpClient.Version.HTTP_3)
                           .build();

Or as explicit preferred version for the request:

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

Nothing else needs changing. After selecting HTTP_3 as preferred version either in the request or in the client, the request can be sent the usual way.

Whether the first request and response will actually be sent via HTTP/3 depends on whether the server supports HTTP/3, which the implementation will determine automatically via the [HTTP alternative services] conventions. If the target server doesn't support HTTP/3 the request may be transparently downgraded to HTTP/2 (or HTTP/1.1). We may extend the HttpRequest.Builder API to support additional configuration to override the default settings.

Only one area of API change is absolutely required, that to override the default protocol version and explicitly request that HTTP/3 be used. However we are also considering the following API enhancements:

Why not make HTTP/3 the default?

We are adding a new constant, HTTP_3, to the existing HttpClient.Version enum. This could cause an IncompatibleClassChangeError if the new constant reaches a switch statement compiled before the addition of the constant, when the switch statement has no default clause. For compatibility reasons, it seems therefore more prudent to require HTTP_3 to be an opt-in: a caller that opts in for HTTP_3 should be prepared to receive responses that contain an HTTP_3 version. A caller that didn't opt-in, such as a caller that was coded before the introduction of the new constant, might not expect it.

In addition, HTTP/3 doesn't define any upgrade mechanism. To figure out whether a server supports HTTP/3, a client would have to either send the first request through HTTP/1.1 or HTTP/2 and hope to receive an [alternate service][9] header or frame, or attempt to initiate a QUIC handshake at the given URL. Because QUIC is based on UDP, determining that QUIC is not supported by the peer can only be achieved by waiting for a response until a timeout expires. Making HTTP_3 the default version would therefore cause a cost to be incurred by each request sent to a new server.

This policy could be revisited in years to come, if adoption of HTTP/3 becomes more widespread.

Testing

We will do extensive testing, including interoperation testing with HTTP servers that support HTTP/3.

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 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 the following RFCs:

The HTTP/3 protocol is defined by the following RFCs:

In addition, the following RFCs are of interest for discovering QUIC endpoints and measuring path MTU:

Some other RFCs are also of interest but may not be supported by the first implementation: