I'm afraid this doesn't appear to be possible in the Java 11 http client API.
The most relevant internal JDK class is Http2Connection.java. It lists the three "creation" cases we'd expect for HTTP/2 connections:
- upgraded HTTP/1.1 plain tcp connection
- prior knowledge directly created plain tcp connection
- directly created HTTP/2 SSL connection which uses ALPN.
We're interested in case 2. There is code for this:
/**
* Cases 2) 3)
*
* request is request to be sent.
*/
private Http2Connection(HttpRequestImpl request,
Http2ClientImpl h2client,
HttpConnection connection)
throws IOException
{
...
}
Alas, this constructor is only actually used from a method for secure connections:
// Requires TLS handshake. So, is really async
static CompletableFuture<Http2Connection> createAsync(HttpRequestImpl request,
Http2ClientImpl h2client,
Exchange<?> exchange) {
assert request.secure();
AbstractAsyncSSLConnection connection = (AbstractAsyncSSLConnection)
...
return connection.connectAsync(exchange)
.thenCompose(unused -> connection.finishConnect())
.thenCompose(unused -> checkSSLConfig(connection))
.thenCompose(notused-> {
CompletableFuture<Http2Connection> cf = new MinimalFuture<>();
try {
Http2Connection hc = new Http2Connection(request, h2client, connection);
cf.complete(hc);
} catch (IOException e) {
cf.completeExceptionally(e);
}
return cf; } );
The non-TLS constructors do only seem to be called when upgrading a HTTP 1.1 connection.
I've looked into many of the other classes, too. I can't see anything that would suggest cleartext HTTP/2 connections can be negotiated without upgrade from HTTP 1.1 in the JDK 11 client.
What I find matches very much with the API description in the javadocs for HttpClient.Builder which only says this about choosing HTTP/2 as the version:
If set to HTTP/2, then each request will attempt to upgrade to HTTP/2.
If the upgrade succeeds, then the response to this request will use
HTTP/2 and all subsequent requests and responses to the same origin
server will use HTTP/2. If the upgrade fails, then the response will
be handled using HTTP/1.1
There is a lot of interesting stuff going on in the HTTP/2 connection reuse. For example, where concurrent requests are upgraded, only one is kept around on for future use in the HTTP/2 connection "cache". The cached connection will expire when the number of streams reaches the maximum, which is ~ 2^31-1, such that further requests won't exceed it, but existing streams will remain open. That's a lot of streams. I think that, if your use case is like mine -- a web service client in very long running applications -- then the cost of the upgrade on practically only the first request is negligable. I'd still like to eliminate it, mainly for simplicity, but I'm starting to think it isn't worth it.
Like the asker I'd like to stick with the JDK client. I will just mention that Apache HttpClient 5 beta appears to support forcing HTTP/2 (HttpVersionPolicy.FORCE_HTTP_2). I can't advise any further on it, because the beta doesn't have much documentation and comments are far sparser than the JDK implementation. But if I try it and have success, I will update my answer.