Databases 21 min read

Understanding HikariCP: Architecture, Core Components, and Usage

This article explains how HikariCP, the default Spring Boot 2.x connection pool, improves database performance through byte‑code optimization, custom containers, and streamlined code, and it details the internal classes, lifecycle methods, configuration, monitoring, and practical usage examples.

New Oriental Technology
New Oriental Technology
New Oriental Technology
Understanding HikariCP: Architecture, Core Components, and Usage

1. Introduction

Database connection pools cache a fixed number of connections to avoid the overhead of creating and destroying connections for each request. HikariCP, the default pool in Spring Boot 2.x, claims to be the fastest Java pool due to several low‑level optimizations.

Byte‑code size reduction (methods kept under 35 bytecodes)

Custom ConcurrentBag container

Custom FastList implementation

Compact code base compared with Druid

The following sections describe the main classes ( HikariDataSource , HikariPool , ConcurrentBag , PoolEntry ) and the internal workflow of HikariCP (version 2.7.9).

2. Overall Description

2.1 Class Relationship Diagram

HikariDataSource implements the DataSource interface and holds the pool configuration; HikariConfig validates the parameters.

2.2 Overall Process Diagram

3. HikariPool

The class com.zaxxer.hikari.pool.HikariPool manages pool initialization, scaling, leak detection and monitoring.

/**
 * Construct a HikariPool with the specified configuration.
 */
public HikariPool(final HikariConfig config) {
    // use configuration to create a DataSource for real connections
    super(config);
    // custom concurrent bag for high‑performance storage
    this.connectionBag = new ConcurrentBag<>(this);
    this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
    this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
    checkFailFast();
    if (config.getMetricsTrackerFactory() != null) {
        setMetricsTrackerFactory(config.getMetricsTrackerFactory());
    } else {
        setMetricRegistry(config.getMetricRegistry());
    }
    setHealthCheckRegistry(config.getHealthCheckRegistry());
    registerMBeans(this);
    ThreadFactory threadFactory = config.getThreadFactory();
    LinkedBlockingQueue
addConnectionQueue = new LinkedBlockingQueue<>(config.getMaximumPoolSize());
    this.addConnectionQueue = unmodifiableCollection(addConnectionQueue);
    this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardPolicy());
    this.closeConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
    this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
    this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
}

After initialization, connections are stored in a ConcurrentBag , which is the actual container for physical connections.

4. ConcurrentBag

ConcurrentBag is a generic resource pool used by HikariCP to store connections (or any object implementing IConcurrentBagEntry ).

4.1 IConcurrentBagEntry

Defines four states:

State

Description

STATE_NOT_IN_USE

Idle

STATE_IN_USE

In use

STATE_REMOVED

Removed

STATE_RESERVED

Reserved (e.g., during shrink)

4.2 PoolEntry

final class PoolEntry implements IConcurrentBagEntry {
    private static final Logger LOGGER = LoggerFactory.getLogger(PoolEntry.class);
    private static final AtomicIntegerFieldUpdater
stateUpdater;
    Connection connection;
    long lastAccessed;
    long lastBorrowed;
    private volatile int state = 0;
    private volatile boolean evict;
    private volatile ScheduledFuture
endOfLife;
    private final FastList
openStatements;
    private final HikariPool hikariPool;
    private final boolean isReadOnly;
    private final boolean isAutoCommit;
}

PoolEntry wraps a physical JDBC connection and stores metadata such as timestamps, eviction flag, and open statements.

4.3 Key Properties of ConcurrentBag

sharedList – holds all resources.

weakThreadLocals – whether thread‑local entries are stored as WeakReference .

threadList – thread‑local cache of resources.

waiters – number of threads waiting for a resource.

closed – pool closed flag.

handoffQueue – a synchronous queue used to block threads when no idle resource is available.

4.4 Core Methods

4.4.1 borrow

Attempts to obtain an idle entry from the thread‑local list, then from the shared list, and finally blocks on handoffQueue if necessary.

public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException {
    final List
list = threadList.get();
    for (int i = list.size() - 1; i >= 0; i--) {
        final Object entry = list.remove(i);
        final T bagEntry = weakThreadLocals ? ((WeakReference
) entry).get() : (T) entry;
        if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
            return bagEntry;
        }
    }
    final int waiting = waiters.incrementAndGet();
    try {
        for (T bagEntry : sharedList) {
            if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
                if (waiting > 1) {
                    listener.addBagItem(waiting - 1);
                }
                return bagEntry;
            }
        }
        listener.addBagItem(waiting);
        timeout = timeUnit.toNanos(timeout);
        do {
            final long start = currentTime();
            final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
            if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
                return bagEntry;
            }
            timeout -= elapsedNanos(start);
        } while (timeout > 10_000);
        return null;
    } finally {
        waiters.decrementAndGet();
    }
}

4.4.2 add

Adds a new idle entry to sharedList and wakes waiting threads.

public void add(final T bagEntry) {
    if (closed) {
        LOGGER.info("ConcurrentBag has been closed, ignoring add()");
        throw new IllegalStateException("ConcurrentBag has been closed, ignoring add()");
    }
    sharedList.add(bagEntry);
    while (waiters.get() > 0 && !handoffQueue.offer(bagEntry)) {
        yield();
    }
}

4.4.3 requite

Returns a resource to the pool, setting its state to STATE_NOT_IN_USE and possibly handing it to waiting threads.

public void requite(final T bagEntry) {
    bagEntry.setState(STATE_NOT_IN_USE);
    for (int i = 0; waiters.get() > 0; i++) {
        if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
            return;
        }
        if ((i & 0xff) == 0xff) {
            parkNanos(MICROSECONDS.toNanos(10));
        } else {
            yield();
        }
    }
    final List
threadLocalList = threadList.get();
    threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
}

4.4.4 remove

Removes a borrowed or reserved entry from the pool.

public boolean remove(final T bagEntry) {
    if (!bagEntry.compareAndSet(STATE_IN_USE, STATE_REMOVED) &&
        !bagEntry.compareAndSet(STATE_RESERVED, STATE_REMOVED) &&
        !closed) {
        LOGGER.warn("Attempt to remove an object from the bag that was not borrowed or reserved: {}", bagEntry);
        return false;
    }
    final boolean removed = sharedList.remove(bagEntry);
    if (!removed && !closed) {
        LOGGER.warn("Attempt to remove an object from the bag that does not exist: {}", bagEntry);
    }
    return removed;
}

4.4.5 reserve / unreserve

Used mainly during pool shrinkage to temporarily protect a connection.

public boolean reserve(final T bagEntry) {
    return bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_RESERVED);
}
public void unreserve(final T bagEntry) {
    if (bagEntry.compareAndSet(STATE_RESERVED, STATE_NOT_IN_USE)) {
        while (waiters.get() > 0 && !handoffQueue.offer(bagEntry)) {
            Thread.yield();
        }
    } else {
        LOGGER.warn("Attempt to relinquish an object to the bag that was not reserved: {}", bagEntry);
    }
}

5. Getting a Connection

The pool lazily initializes on the first request, validates configuration, acquires the suspendResumeLock , and then calls connectionBag.borrow(timeout, MILLISECONDS) . If the timeout expires, null is returned and an exception is thrown.

6. Closing a Connection

void closeConnection(final PoolEntry poolEntry, final String closureReason) {
    if (connectionBag.remove(poolEntry)) {
        final Connection connection = poolEntry.close();
        closeConnectionExecutor.execute(() -> {
            quietlyCloseConnection(connection, closureReason);
            if (poolState == POOL_NORMAL) {
                fillPool();
            }
        });
    }
}

The removal is asynchronous to keep the main thread lightweight.

7. Pool Expansion

private synchronized void fillPool() {
    final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(),
                                         config.getMinimumIdle() - getIdleConnections())
                                 - addConnectionQueue.size();
    for (int i = 0; i < connectionsToAdd; i++) {
        addConnectionExecutor.submit((i < connectionsToAdd - 1) ? POOL_ENTRY_CREATOR : POST_FILL_POOL_ENTRY_CREATOR);
    }
}

The method ensures that the pool maintains at least minimumIdle idle connections while respecting maximumPoolSize .

8. Using HikariCP in Spring Boot

Typical YAML configuration for a MySQL datasource:

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo_db?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: ENC(iTgGY9Zl+EqWRfyqfRN54/54nTvXIAsY)
    hikari:
      maximum-pool-size: 10
      idle-timeout: 180000
      auto-commit: true
      pool-name: defaultHikariPool
      max-lifetime: 1800000
      connection-timeout: 30000
      connection-test-query: SELECT 1
JavaperformanceConcurrencyConnection PoolSpring BootHikariCP
New Oriental Technology
Written by

New Oriental Technology

Practical internet development experience, tech sharing, knowledge consolidation, and forward-thinking insights.

0 followers
Reader feedback

How this landed with the community

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