Why Object Pools Boost Java Performance: Deep Dive into Commons Pool 2 and HikariCP

This article explains the motivation behind object pooling in Java, walks through the core classes of Commons Pool 2 with a Jedis example, details key configuration parameters, presents JMH benchmark results that show a five‑fold throughput gain, and analyzes HikariCP’s design tricks and common interview questions about pool tuning.

IT Architects Alliance
IT Architects Alliance
IT Architects Alliance
Why Object Pools Boost Java Performance: Deep Dive into Commons Pool 2 and HikariCP

Background

In typical Java code we often keep objects that are expensive to create, such as thread resources, database connections, or TCP sockets. Repeatedly creating and destroying these objects consumes significant system resources, so a virtual pool can store them and provide fast retrieval when needed.

Commons Pool 2 Overview

Commons Pool 2 is a widely used generic object‑pooling library. Adding it to a Maven project requires the following dependency:

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-pool2</artifactId>
  <version>2.11.1</version>
</dependency>

The central class is GenericObjectPool, which is instantiated with a PooledObjectFactory and a GenericObjectPoolConfig:

public GenericObjectPool(final PooledObjectFactory factory, final GenericObjectPoolConfig config) { … }

Example: Jedis with Commons Pool 2

Redis’s popular client Jedis uses Commons Pool 2 to manage its connection pool. The factory’s makeObject method creates a Jedis instance, performs the time‑consuming connection steps, and wraps the result in a DefaultPooledObject:

@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;
    }
}

Pool Lifecycle and Configuration

When a client calls borrowObject, the pool first tries to poll an idle object from a LinkedBlockingDeque. If none is available, the factory creates a new instance. The method’s skeleton looks like this:

public T borrowObject(final Duration borrowMaxWaitDuration) throws Exception {
    // omitted: waiting logic
    while (p == null) {
        p = idleObjects.pollFirst(); // try to get from pool
        if (p == null) {
            p = create(); // create new if pool empty
            if (p != null) { create = true; }
        }
    }
    // omitted: post‑processing
}

Key configuration fields are defined in GenericObjectPoolConfig (and its superclass BaseObjectPoolConfig), for example:

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;

Understanding these parameters helps tune the pool for latency, throughput, and resource usage.

Performance Test with JMH

A JMH benchmark compares a Redis operation performed with a pooled JedisPool versus a raw Jedis instance. The benchmark class is:

@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();
    }
}

The resulting meta‑chart shows that the pooled version achieves roughly five times higher throughput than the non‑pooled version.

JMH benchmark result
JMH benchmark result

HikariCP: A Fast Database Connection Pool

HikariCP (named after the Japanese word for “light”) is the default connection pool in Spring Boot. It follows the same basic pooling principles but adds several low‑level optimizations that make it exceptionally fast.

HikariCP performance chart
HikariCP performance chart

Three main tricks give HikariCP its edge:

Uses FastList instead of ArrayList to avoid bounds‑check overhead.

Optimizes bytecode with Javassist, replacing virtual calls with static invocations.

Implements a lock‑free ConcurrentBag to reduce contention.

Typical Interview Questions

Interviewers often ask how to set the timeout ( maxWaitMillis) and what pool size is appropriate. A practical rule is to keep the timeout within the maximum latency the service can tolerate (e.g., 500‑1000 ms for a 10 ms normal response). For database pools, 20‑50 connections are usually sufficient; setting the size to hundreds is a common misconception.

HikariCP recommends not configuring minimumIdle explicitly; it defaults to the same value as maximumPoolSize. If the database server has many idle connections, the dynamic adjustment can be disabled.

Result Cache Pool Concept

The article introduces the notion of a “Result Cache Pool”, which blends caching and pooling ideas: objects are processed once, stored in a fast‑access area, and reused without recomputation. This concept applies to scenarios such as JSP compilation, static‑HTML generation, or any situation where the result of an expensive operation can be cached and served repeatedly.

Summary

Starting from Commons Pool 2, we explored how object pools reduce creation cost, examined key configuration knobs, and demonstrated a five‑fold performance boost with JMH. We then looked at HikariCP’s additional low‑level optimizations and discussed common interview questions about timeout and pool sizing. Finally, we presented the broader “Result Cache Pool” idea, encouraging readers to apply pooling principles to other resource‑intensive components such as HTTP clients, RPC frameworks, or custom caches.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaPerformance OptimizationHikariCPobject poolDatabase ConnectionCommons Pool 2JMH Benchmark
IT Architects Alliance
Written by

IT Architects Alliance

Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.