Eclipse Jetty HTTP Client - A lightweight, asynchronous HTTP client library that supports HTTP/1.1, HTTP/2, WebSocket, and various authentication mechanisms, proxy configurations, and connection pooling strategies.
—
The connection pooling capability provides multiple connection pool strategies for optimizing connection reuse and performance, including duplex, multiplex, round-robin, and random selection pools with comprehensive connection lifecycle management.
The base interface for all connection pool implementations.
public interface ConnectionPool extends Closeable {
// Connection acquisition and release
CompletableFuture<Connection> acquire(boolean create);
boolean release(Connection connection);
boolean remove(Connection connection);
// Pool management
void close();
boolean isClosed();
// Pool statistics
int getConnectionCount();
int getIdleConnectionCount();
int getActiveConnectionCount();
// Pool configuration
int getMaxConnectionCount();
// Preconnection
CompletableFuture<Void> preCreateConnections(int connectionCount);
}Connection pool for HTTP/1.1 duplex connections where each connection can handle one request at a time.
public class DuplexConnectionPool extends AbstractConnectionPool {
public DuplexConnectionPool(Destination destination, int maxConnections, Callback requester);
public DuplexConnectionPool(Destination destination, int maxConnections, boolean cache, Callback requester);
// Configuration
public boolean isCache();
public int getMaxDuplexCount();
}// Configure HttpClient with duplex connection pool
HttpClient client = new HttpClient();
client.start();
// Default connection pool settings
// - Max connections per destination: typically 64
// - Connection caching: enabled
// - Suitable for HTTP/1.1 connections
// Make concurrent requests - each uses separate connection
List<CompletableFuture<ContentResponse>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
CompletableFuture<ContentResponse> future = client.newRequest("https://api.example.com/data/" + i).send();
futures.add(future);
}
// Wait for all requests to complete
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> {
System.out.println("All requests completed");
futures.forEach(future -> {
try {
ContentResponse response = future.get();
System.out.println("Response: " + response.getStatus());
} catch (Exception e) {
System.err.println("Request failed: " + e.getMessage());
}
});
});Connection pool for HTTP/2 multiplex connections where each connection can handle multiple concurrent requests.
public class MultiplexConnectionPool extends AbstractConnectionPool {
public MultiplexConnectionPool(Destination destination, int maxConnections, Callback requester, int maxMultiplex);
// Configuration
public int getMaxMultiplex();
}// Configure HttpClient for HTTP/2 with multiplex connection pool
HttpClient client = new HttpClient();
// Enable HTTP/2
client.setProtocols(List.of("h2", "http/1.1"));
client.start();
// HTTP/2 connections can multiplex many requests over single connection
// Default max multiplex: typically 1024 streams per connection
// Make many concurrent requests over fewer connections
List<CompletableFuture<ContentResponse>> futures = new ArrayList<>();
for (int i = 0; i < 100; i++) {
CompletableFuture<ContentResponse> future = client.newRequest("https://http2.example.com/api/data/" + i).send();
futures.add(future);
}
// All requests may use the same HTTP/2 connection with multiplexing
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> System.out.println("All HTTP/2 requests completed"));Connection pool that distributes requests across available connections in round-robin fashion.
public class RoundRobinConnectionPool extends AbstractConnectionPool {
public RoundRobinConnectionPool(Destination destination, int maxConnections, Callback requester);
public RoundRobinConnectionPool(Destination destination, int maxConnections, Callback requester, int maxMultiplex);
}// Round-robin is useful for load balancing across multiple connections
// Typically used with HTTP/1.1 where you want to distribute load evenly
public class RoundRobinClient {
private final HttpClient client;
public RoundRobinClient() throws Exception {
this.client = new HttpClient();
// Configure connection factory to use round-robin pool
// Note: Direct pool configuration is typically done at transport level
client.start();
}
public void makeBalancedRequests() throws Exception {
// Make requests that will be distributed round-robin across connections
for (int i = 0; i < 20; i++) {
final int requestId = i;
client.newRequest("https://api.example.com/endpoint/" + i)
.send(result -> {
if (result.isSucceeded()) {
System.out.println("Request " + requestId + " completed on connection: " +
result.getRequest().getConnection());
}
});
}
}
}Connection pool that randomly selects from available connections.
public class RandomConnectionPool extends AbstractConnectionPool {
public RandomConnectionPool(Destination destination, int maxConnections, Callback requester);
public RandomConnectionPool(Destination destination, int maxConnections, Callback requester, int maxMultiplex);
}// Random selection can help avoid hot-spotting on specific connections
// Useful when you want to distribute load unpredictably
public class RandomizedLoadClient {
public void demonstrateRandomPool() {
// Random connection selection helps with:
// - Avoiding connection hot-spots
// - Better distribution in some network topologies
// - Testing resilience across different connections
// Requests will be randomly distributed across available connections
for (int i = 0; i < 50; i++) {
client.GET("https://distributed.example.com/api/data");
}
}
}Connection pool that validates connections before use to ensure they are still functional.
public class ValidatingConnectionPool extends DuplexConnectionPool {
public ValidatingConnectionPool(Destination destination, int maxConnections, Callback requester);
// Validation configuration
public void setValidationTimeout(long validationTimeout);
public long getValidationTimeout();
}// Validating pool is useful for long-lived connections that might become stale
// Automatically validates connections before reuse
public class ValidatedClient {
public void configureValidation() throws Exception {
HttpClient client = new HttpClient();
// Configuration for connection validation
// Validates connections that have been idle for a certain period
client.setIdleTimeout(30000); // 30 seconds idle timeout
client.start();
// Long pause between requests to demonstrate validation
ContentResponse response1 = client.GET("https://api.example.com/data");
System.out.println("First request: " + response1.getStatus());
// Wait longer than idle timeout
Thread.sleep(35000);
// Next request will validate/recreate connection if needed
ContentResponse response2 = client.GET("https://api.example.com/data");
System.out.println("Second request: " + response2.getStatus());
client.stop();
}
}public class PoolConfigurationExample {
public void configureConnectionPools() throws Exception {
HttpClient client = new HttpClient();
// Configure maximum connections per destination
client.setMaxConnectionsPerDestination(20);
// Configure request queue size per destination
client.setMaxRequestsQueuedPerDestination(50);
// Configure connection idle timeout
client.setIdleTimeout(60000); // 60 seconds
// Configure connection establishment timeout
client.setConnectTimeout(10000); // 10 seconds
client.start();
// These settings affect all connection pools for this client
System.out.println("Max connections per destination: " + client.getMaxConnectionsPerDestination());
System.out.println("Idle timeout: " + client.getIdleTimeout());
}
}public class CustomConnectionPoolFactory {
public static ConnectionPool createCustomPool(Destination destination, int maxConnections) {
// Create a custom connection pool based on requirements
if (destination.getScheme().equals("https") && supportsHttp2(destination)) {
// Use multiplex pool for HTTP/2 HTTPS connections
return new MultiplexConnectionPool(destination, maxConnections, null, 100);
} else if (isHighVolumeDestination(destination)) {
// Use round-robin for high-volume destinations
return new RoundRobinConnectionPool(destination, maxConnections, null);
} else if (isUnreliableNetwork(destination)) {
// Use validating pool for unreliable networks
return new ValidatingConnectionPool(destination, maxConnections, null);
} else {
// Default to duplex pool
return new DuplexConnectionPool(destination, maxConnections, null);
}
}
private static boolean supportsHttp2(Destination destination) {
// Logic to determine if destination supports HTTP/2
return true; // Simplified
}
private static boolean isHighVolumeDestination(Destination destination) {
// Logic to identify high-volume destinations
return destination.getHost().contains("api") || destination.getHost().contains("cdn");
}
private static boolean isUnreliableNetwork(Destination destination) {
// Logic to identify destinations with unreliable connections
return destination.getHost().contains("mobile") || destination.getHost().contains("satellite");
}
}public class ConnectionPoolMonitor {
private final HttpClient client;
public ConnectionPoolMonitor(HttpClient client) {
this.client = client;
}
public void printPoolStatistics() {
// Note: Direct access to connection pools requires internal APIs
// This is conceptual - actual implementation may vary
System.out.println("Connection Pool Statistics:");
System.out.println("Max connections per destination: " + client.getMaxConnectionsPerDestination());
System.out.println("Current destinations: " + getDestinationCount());
// Per-destination statistics would require accessing internal destination manager
printDestinationStats();
}
private void printDestinationStats() {
// Conceptual implementation for monitoring
System.out.println("Destination Statistics:");
System.out.println(" api.example.com: 5 active, 3 idle connections");
System.out.println(" cdn.example.com: 2 active, 8 idle connections");
}
private int getDestinationCount() {
// Would access internal destination manager
return 0; // Placeholder
}
public void scheduleMonitoring(ScheduledExecutorService scheduler) {
scheduler.scheduleAtFixedRate(this::printPoolStatistics, 0, 30, TimeUnit.SECONDS);
}
}public class ConnectionLifecycleMonitor {
public void monitorConnections(HttpClient client) {
// Add request listeners to monitor connection usage
client.getRequestListeners().addListener(new Request.Listener() {
@Override
public void onBegin(Request request) {
Connection connection = request.getConnection();
if (connection != null) {
System.out.println("Request started on connection: " + connection);
}
}
@Override
public void onSuccess(Request request) {
Connection connection = request.getConnection();
if (connection != null) {
System.out.println("Request completed on connection: " + connection);
}
}
});
}
}public class PoolPreheating {
public void preheatConnections(HttpClient client, String... urls) throws Exception {
List<CompletableFuture<Void>> preheatFutures = new ArrayList<>();
for (String url : urls) {
// Make lightweight requests to establish connections
CompletableFuture<Void> future = client.newRequest(url)
.method(HttpMethod.HEAD) // Use HEAD to minimize data transfer
.send()
.thenApply(response -> null); // Convert to Void
preheatFutures.add(future);
}
// Wait for all preheat requests to complete
CompletableFuture.allOf(preheatFutures.toArray(new CompletableFuture[0]))
.get(30, TimeUnit.SECONDS);
System.out.println("Connection pools preheated for " + urls.length + " destinations");
}
}
// Usage
PoolPreheating preheater = new PoolPreheating();
preheater.preheatConnections(client,
"https://api.example.com",
"https://cdn.example.com",
"https://auth.example.com"
);public class DynamicPoolManager {
private final HttpClient client;
private final Map<String, Integer> destinationLoad;
public DynamicPoolManager(HttpClient client) {
this.client = client;
this.destinationLoad = new ConcurrentHashMap<>();
}
public void adjustPoolSizes() {
destinationLoad.forEach((destination, load) -> {
int recommendedSize = calculateOptimalPoolSize(load);
adjustPoolSize(destination, recommendedSize);
});
}
private int calculateOptimalPoolSize(int load) {
// Calculate optimal pool size based on load
if (load > 100) return 50;
if (load > 50) return 25;
if (load > 10) return 10;
return 5;
}
private void adjustPoolSize(String destination, int size) {
// Conceptual - actual implementation would require internal APIs
System.out.println("Adjusting pool size for " + destination + " to " + size);
}
public void recordRequest(String destination) {
destinationLoad.merge(destination, 1, Integer::sum);
}
public void scheduleOptimization(ScheduledExecutorService scheduler) {
scheduler.scheduleAtFixedRate(this::adjustPoolSizes, 0, 60, TimeUnit.SECONDS);
}
}public class PoolBestPractices {
public HttpClient createOptimalClient() throws Exception {
HttpClient client = new HttpClient();
// Conservative connection limits to avoid overwhelming servers
client.setMaxConnectionsPerDestination(20);
client.setMaxRequestsQueuedPerDestination(100);
// Reasonable timeouts
client.setConnectTimeout(10000); // 10 seconds
client.setIdleTimeout(60000); // 60 seconds
// Enable connection keep-alive
client.setTCPNoDelay(true);
// Configure for HTTP/2 when available
client.setProtocols(List.of("h2", "http/1.1"));
client.start();
return client;
}
public void demonstrateEfficientUsage(HttpClient client) throws Exception {
// Reuse client across multiple requests for connection pooling benefits
List<CompletableFuture<ContentResponse>> futures = new ArrayList<>();
// Make multiple requests to same destination - will reuse connections
for (int i = 0; i < 10; i++) {
CompletableFuture<ContentResponse> future = client.newRequest("https://api.example.com/data/" + i).send();
futures.add(future);
}
// Process responses as they complete
for (CompletableFuture<ContentResponse> future : futures) {
future.thenAccept(response -> {
System.out.println("Response: " + response.getStatus());
// Connection automatically returned to pool after response processing
});
}
// Don't stop client until all operations are complete
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
}public class ResourceManagedClient implements AutoCloseable {
private final HttpClient client;
public ResourceManagedClient() throws Exception {
this.client = new HttpClient();
configureClient();
client.start();
}
private void configureClient() {
// Optimize for connection reuse
client.setMaxConnectionsPerDestination(15);
client.setIdleTimeout(30000);
// Add shutdown hook for graceful cleanup
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
close();
} catch (Exception e) {
System.err.println("Error during client shutdown: " + e.getMessage());
}
}));
}
public ContentResponse get(String url) throws Exception {
return client.GET(url);
}
@Override
public void close() throws Exception {
// Graceful shutdown - allows existing requests to complete
client.stop();
}
}
// Usage with try-with-resources
try (ResourceManagedClient client = new ResourceManagedClient()) {
ContentResponse response = client.get("https://api.example.com/data");
System.out.println("Response: " + response.getStatus());
} // Automatically closes and cleans up connection poolsInstall with Tessl CLI
npx tessl i tessl/maven-org-eclipse-jetty--jetty-client