HTTP/2 Java Client — Sample Code

What this is: A standalone sample client (Http2JsonClient) that demonstrates how to call Axis2 JSON-RPC services over HTTP/2 from plain Java using Apache HttpClient 5. It is not part of the Axis2 framework — it is example code in the userguide samples that you can copy and adapt for your own project.

Why it exists: Java's built-in HttpURLConnection does not support HTTP/2. Apache HttpClient 5's convenience classes (SimpleHttpRequest / SimpleHttpResponse) support HTTP/2 but silently buffer the entire response in memory, defeating the streaming benefit. This sample shows the correct pattern — using AbstractBinResponseConsumer with the async API — so you don't have to rediscover it the hard way.

Two execution modes:

  • Buffered — returns the full response as a String. Simple, suitable for responses that fit in memory.
  • Streaming — writes response bytes to an OutputStream in 64KB chunks as HTTP/2 DATA frames arrive. Memory stays flat regardless of response size. When paired with the Streaming JSON Formatter (AXIS2-6103), data flows end-to-end in 64KB chunks.

The SimpleHttp* Pitfall

Apache HttpClient 5 provides SimpleHttpRequest and SimpleHttpResponse as convenience classes for async requests. Do not use them for HTTP/2 workloads with large responses. They appear to work, but they silently defeat HTTP/2 streaming.

SimpleHttpResponse is a buffering response object — it accumulates the entire response body in memory before returning it to the caller. For a 100MB response:

  • SimpleHttpResponse: allocates 100MB+ of heap (internal byte arrays, header maps, content type parsing) before your code sees a single byte
  • AbstractBinResponseConsumer: your data(ByteBuffer) callback fires for each 64KB HTTP/2 DATA frame — memory stays flat at ~64KB working set

This is not obvious from the HttpClient 5 documentation, and it is easy to write code that uses SimpleHttpResponse, observes correct HTTP/2 ALPN negotiation in the logs, and concludes that HTTP/2 streaming is working — when in fact the response is fully buffered before your code runs. The sample client avoids this by using AbstractBinResponseConsumer for all requests, including the buffered convenience method.

Dependencies

Requires Java 11+ (ALPN built in) and Apache HttpClient 5.4+:


<!-- Maven -->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.4.3</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents.core5</groupId>
    <artifactId>httpcore5-h2</artifactId>
    <version>5.4.1</version>
</dependency>

These are the same dependencies used by Axis2's own H2TransportSender. If you are already running Axis2 with HTTP/2 transport, they are already on your classpath.

Buffered Execution

POST JSON-RPC to any Axis2 service, get the response as a String:


String url = "https://localhost:8443/axis2-json-api/services/FinancialBenchmarkService";
String json = "{\"monteCarlo\":[{\"arg0\":{\"nSimulations\":100000,\"nPeriods\":252,"
    + "\"initialValue\":1000000,\"expectedReturn\":0.10,\"volatility\":0.223,"
    + "\"nPeriodsPerYear\":252,\"randomSeed\":42}}]}";

String response = Http2JsonClient.execute(url, json, 300);
System.out.println(response);

Http2JsonClient.shutdown();

The client negotiates HTTP/2 via ALPN on the TLS handshake. Connections are pooled and multiplexed — multiple concurrent requests share a single TCP connection.

Streaming Execution

For large responses (10MB+), stream to a file or parser instead of buffering in heap:


String url = "https://localhost:8443/axis2-json-api/services/BigDataH2Service";
String json = "{\"generate\":[{\"arg0\":{\"datasetSize\":52428800}}]}";

try (FileOutputStream fos = new FileOutputStream("/tmp/result.json")) {
    int status = Http2JsonClient.executeStreaming(url, json, 300, fos);
    System.out.println("HTTP " + status);
}

Http2JsonClient.shutdown();

Each HTTP/2 DATA frame triggers a callback that writes directly to your OutputStream. The capacityIncrement() returns 64KB, creating natural HTTP/2 flow control backpressure — the client tells the server "I can accept 64KB more" after each chunk.

When the server uses the Streaming JSON Formatter, data flows end-to-end without full-body buffering on either side:


Server (MoshiStreamingMessageFormatter)
  → FlushingOutputStream flushes every 64KB
  → HTTP/2 DATA frames
  → Http2JsonClient.data() callback
  → your OutputStream

Timeout and Cancellation

Both methods accept a timeoutSeconds parameter for Future.get(). If the timeout expires or the thread is interrupted, the underlying HTTP request is cancelled to prevent zombie requests that would continue consuming resources:


try {
    response = future.get(timeoutSeconds, TimeUnit.SECONDS);
} catch (Exception e) {
    requestFuture.cancel(true);  // Cancel the HTTP request
    if (e instanceof InterruptedException) {
        Thread.currentThread().interrupt();  // Restore interrupt flag
    }
    throw e;
}

Source Code

The complete sample client is available on GitHub:

Http2JsonClient.java on GitHub

Copy and adapt it for your project. It has no dependency on Axis2 itself — only Apache HttpClient 5 and httpcore5-h2.