Connection Pool Implementation
Build a generic connection pool with size limits, idle timeout, health checks, and metrics.
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.time.Instant;
public class ConnectionPool<T> {
private final BlockingQueue<PooledConnection<T>> available;
private final AtomicInteger totalCreated = new AtomicInteger(0);
private final int maxSize;
private final long maxIdleMs;
private final Supplier<T> factory;
private final java.util.function.Consumer<T> destroyer;
record PooledConnection<T>(T connection, Instant createdAt, Instant lastUsed) {}
public ConnectionPool(
int maxSize,
long maxIdleMs,
Supplier<T> factory,
java.util.function.Consumer<T> destroyer
) {
this.maxSize = maxSize;
this.maxIdleMs = maxIdleMs;
this.factory = factory;
this.destroyer = destroyer;
this.available = new LinkedBlockingQueue<>(maxSize);
}
public T acquire(long timeoutMs) throws Exception {
// Try to get existing connection
PooledConnection<T> pooled = available.poll();
if (pooled != null) {
if (!isExpired(pooled)) {
return pooled.connection();
}
destroy(pooled);
}
// Create new if under limit
if (totalCreated.get() < maxSize) {
totalCreated.incrementAndGet();
return factory.get();
}
// Wait for one to be returned
pooled = available.poll(timeoutMs, TimeUnit.MILLISECONDS);
if (pooled == null) {
throw new TimeoutException("Connection pool exhausted");
}
return pooled.connection();
}
public void release(T connection) {
var pooled = new PooledConnection<>(connection, Instant.now(), Instant.now());
if (!available.offer(pooled)) {
destroy(pooled);
}
}
private boolean isExpired(PooledConnection<T> pooled) {
return Instant.now().toEpochMilli() - pooled.lastUsed().toEpochMilli() > maxIdleMs;
}
private void destroy(PooledConnection<T> pooled) {
totalCreated.decrementAndGet();
destroyer.accept(pooled.connection());
}
public int available() { return available.size(); }
public int total() { return totalCreated.get(); }
// Auto-closeable wrapper
public PooledResource<T> acquireResource(long timeoutMs) throws Exception {
T conn = acquire(timeoutMs);
return new PooledResource<>(conn, this);
}
public static class PooledResource<T> implements AutoCloseable {
private final T resource;
private final ConnectionPool<T> pool;
PooledResource(T resource, ConnectionPool<T> pool) {
this.resource = resource;
this.pool = pool;
}
public T get() { return resource; }
@Override
public void close() { pool.release(resource); }
}
public static void main(String[] args) throws Exception {
var pool = new ConnectionPool<>(5, 30000,
() -> "Connection-" + System.nanoTime(),
conn -> System.out.println("Destroyed: " + conn)
);
try (var res = pool.acquireResource(5000)) {
System.out.println("Using: " + res.get());
} // auto-released
System.out.printf("Pool: %d available, %d total%n", pool.available(), pool.total());
}
}Use Cases
- Database connection pooling
- HTTP client connection management
- Resource pooling for expensive object creation
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
CompletableFuture — Async Programming
Chain async operations with CompletableFuture: thenApply, thenCompose, allOf, and exception handling.
Best for: Parallel API calls to multiple services
Virtual Threads — Lightweight Concurrency
Use Java 21 virtual threads for massive concurrency without thread pool tuning or reactive frameworks.
Best for: High-concurrency web servers handling thousands of requests
ExecutorService — Thread Pool Management
Create and manage thread pools with ExecutorService: fixed, cached, scheduled, and custom pools.
Best for: Managing concurrent task execution
Concurrent Collections — Thread-Safe Maps
Use ConcurrentHashMap, CopyOnWriteArrayList, and BlockingQueue for thread-safe data structures.
Best for: Thread-safe caching in multi-threaded applications