Java Connection Pooling: Commons Pool 2, Jedis, and HikariCP
This article explains the principles of object pooling in Java, introduces the Commons Pool 2 library, demonstrates its use with Redis client Jedis, compares performance with JMH benchmarks, and discusses the high‑performance HikariCP database connection pool, including configuration tips and interview questions.
In typical Java applications, creating expensive resources such as threads, database connections, or TCP sockets repeatedly can cause significant performance loss; pooling these objects allows them to be reused after a lightweight reset.
The article first introduces the widely used Apache Commons Pool 2 library, showing how to add it via Maven:
org.apache.commons
commons-pool2
2.11.1The core class GenericObjectPool creates a pool by receiving a PooledObjectFactory and a GenericObjectPoolConfig :
public GenericObjectPool(
final PooledObjectFactory
factory,
final GenericObjectPoolConfig
config)As a concrete example, the Redis client Jedis uses Commons Pool to manage its connections. The factory class implements makeObject to create a PooledObject wrapped by new DefaultPooledObject<>(obj) :
@Override
public PooledObject
makeObject() throws Exception {
Jedis jedis = null;
try {
jedis = new Jedis(jedisSocketFactory, clientConfig);
// time‑consuming operations
jedis.connect();
return new DefaultPooledObject<>(jedis);
} catch (JedisException je) {
if (jedis != null) {
try { jedis.quit(); } catch (RuntimeException e) { logger.warn("Error while QUIT", e); }
try { jedis.close(); } catch (RuntimeException e) { logger.warn("Error while close", e); }
}
throw je;
}
}When a client borrows an object, the pool first tries to poll an idle instance from a LinkedBlockingDeque ; if none is available it creates a new one via the factory:
public T borrowObject(final Duration borrowMaxWaitDuration) throws Exception {
// omitted lines
while (p == null) {
create = false;
// try to get from idle queue
p = idleObjects.pollFirst();
if (p == null) {
p = create();
if (p != null) { create = true; }
}
// omitted lines
}
// omitted lines
}The configuration class GenericObjectPoolConfig contains many parameters, such as maxTotal , maxIdle , minIdle , maxWaitMillis , blockWhenExhausted , and several test* flags that control validation on create, borrow, return, and idle checks.
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
private boolean lifo = DEFAULT_LIFO;
private boolean fairness = DEFAULT_FAIRNESS;
private long maxWaitMillis = DEFAULT_MAX_WAIT_MILLIS;
private long minEvictableIdleTimeMillis = DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
private long softMinEvictableIdleTimeMillis = DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
private int numTestsPerEvictionRun = DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
private boolean testOnCreate = DEFAULT_TEST_ON_CREATE;
private boolean testOnBorrow = DEFAULT_TEST_ON_BORROW;
private boolean testOnReturn = DEFAULT_TEST_ON_RETURN;
private boolean testWhileIdle = DEFAULT_TEST_WHILE_IDLE;
private long timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
private boolean blockWhenExhausted = DEFAULT_BLOCK_WHEN_EXHAUSTED;Typical interview questions focus on setting timeout values ( maxWaitMillis ) and eviction parameters ( minEvictableIdleTimeMillis , softMinEvictableIdleTimeMillis ) to balance resource usage and responsiveness.
A JMH benchmark compares pooled versus non‑pooled Redis access, showing roughly a 5× throughput improvement when using a pool:
@Fork(2)
@State(Scope.Benchmark)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@BenchmarkMode(Mode.Throughput)
public class JedisPoolVSJedisBenchmark {
JedisPool pool = new JedisPool("localhost", 6379);
@Benchmark
public void testPool() {
Jedis jedis = pool.getResource();
jedis.set("a", UUID.randomUUID().toString());
jedis.close();
}
@Benchmark
public void testJedis() {
Jedis jedis = new Jedis("localhost", 6379);
jedis.set("a", UUID.randomUUID().toString());
jedis.close();
}
// omitted lines
}The article then shifts to the database connection pool HikariCP, which is the default pool in Spring Boot. HikariCP achieves high performance through three main techniques: using FastList instead of ArrayList , byte‑code optimization with Javassist (replacing invokevirtual with invokestatic ), and a lock‑free ConcurrentBag .
Key configuration parameters for HikariCP include maximumPoolSize and minimumIdle . In practice, a pool size of 20‑50 connections is sufficient for most databases; setting it excessively high can waste resources.
HikariCP relies on Connection.isValid() for health checks, eliminating the need for the many test* flags present in other pools.
The concept of a "Result Cache Pool" is introduced, highlighting the similarity between pooling and caching: both store processed objects for faster subsequent access.
Finally, the article summarizes the main points: object pooling reduces creation cost, proper configuration of pool size and timeout is essential, monitoring pool metrics is crucial, and the same pooling ideas apply to HTTP clients, RPC frameworks, and thread pools.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.