javaadvanced

Connection Pool Implementation

Build a generic connection pool with size limits, idle timeout, health checks, and metrics.

java
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.