Optimizing Apache HttpClient for High-Concurrency Scenarios
This article explains how to improve a high‑traffic Java service by reusing a singleton HttpClient, enabling keep‑alive, configuring a pooling connection manager, and adding an idle‑connection monitor, which reduces average request latency from 250 ms to about 80 ms.
Background: The service calls an HTTP‑based API with tens of millions of daily requests, originally using a new HttpClient for each request, resulting in an average latency of 250 ms.
Analysis: Identified three major overheads – repeated creation of HttpClient instances, repeated TCP handshakes, and unnecessary duplication of response entities.
Implementation:
1. Define a keep‑alive strategy to reuse connections.
ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
return Long.parseLong(value) * 1000;
}
}
return 60 * 1000; // default 60 seconds
}
};2. Configure a PoolingHttpClientConnectionManager.
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(500);
connectionManager.setDefaultMaxPerRoute(50); // adjust per business3. Build a singleton HttpClient with the manager and keep‑alive strategy.
httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.setKeepAliveStrategy(myStrategy)
.setDefaultRequestConfig(RequestConfig.custom().setStaleConnectionCheckEnabled(true).build())
.build();4. Add an idle‑connection monitor thread to close expired and idle connections.
public static class IdleConnectionMonitorThread extends Thread {
private final HttpClientConnectionManager connMgr;
private volatile boolean shutdown;
public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) { this.connMgr = connMgr; }
@Override
public void run() {
try {
while (!shutdown) {
synchronized (this) {
wait(5000);
connMgr.closeExpiredConnections();
connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
}
}
} catch (InterruptedException ex) { }
}
public void shutdown() {
shutdown = true;
synchronized (this) { notifyAll(); }
}
}5. Use a ResponseHandler to automatically consume the entity and avoid manual stream handling.
public
T execute(final HttpHost target, final HttpRequest request,
final ResponseHandler
responseHandler, final HttpContext context)
throws IOException, ClientProtocolException {
Args.notNull(responseHandler, "Response handler");
final HttpResponse response = execute(target, request, context);
final T result;
try {
result = responseHandler.handleResponse(response);
} catch (final Exception t) {
final HttpEntity entity = response.getEntity();
try { EntityUtils.consume(entity); } catch (final Exception t2) { log.warn("Error consuming content after an exception.", t2); }
if (t instanceof RuntimeException) throw (RuntimeException) t;
if (t instanceof IOException) throw (IOException) t;
throw new UndeclaredThrowableException(t);
}
final HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
return result;
}Result: After applying these changes, the average request latency dropped from about 250 ms to roughly 80 ms, eliminating frequent thread‑exhaustion alarms.
Additional notes: configure connection and socket timeouts, optionally adjust Nginx keep‑alive settings, and include Maven dependency for HttpClient 4.5.6.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.