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
OutputStreamin 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 byteAbstractBinResponseConsumer: yourdata(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.
Apache Axis2
