HikariCP Overview (Part 1): Initialization, Core Components, Monitoring and Configuration
This article provides a detailed analysis of HikariCP’s initialization, core components, startup flow, connection acquisition logic, monitoring metrics, and key configuration parameters, illustrating how Spring Boot 2.x leverages this high‑performance JDBC connection pool and offering guidance for tuning and extending it.
HikariCP Overview (Part 1)
Spring Boot 2.x officially adopts HikariCP as the default JDBC connection pool. As a next‑generation pool, HikariCP delivers excellent performance, a compact codebase, and a concurrency design worth studying.
Initialization Process
Core Components
We first analyze the initialization process of HikariCP . The diagram below shows the overall logic of obtaining a connection and the main classes that implement most of the pool’s functionality:
HikariDataSource – the default data‑source implementation loaded by Spring Boot 2.x. It holds a reference to a HikariPool object and is used directly by upper‑level services.
HikariPool – the actual pool manager providing connection acquisition, discard, close and recycle capabilities. Internally it holds a ConcurrentBag instance.
ConcurrentBag – the container that really stores the connections. It maintains a CopyOnWriteArrayList called sharedList and a thread‑local threadList for per‑thread caching.
ProxyFactory – generates the wrapper class HikariProxyConnection . It uses Javassist to create the proxy; the concrete implementation is JavassistProxyFactory .
Startup Flow
The service startup loads the datasource beans, but the actual HikariPool is not created until the first connection request.
@Bean(name = "agreementDataSource")
@ConfigurationProperties(prefix = "mybatis")
public DataSource agreementDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "readSource")
@ConfigurationProperties(prefix = "mybatis.read")
public DataSource readSource() {
return DataSourceBuilder.create().build();
}Only the DataSource bean is instantiated here; the internal HikariPool remains null and will be lazily created when getConnection() is called for the first time.
First Connection Acquisition
@Override
public Connection getConnection() throws SQLException {
if (isClosed()) {
throw new SQLException("HikariDataSource " + this + " has been closed.");
}
if (fastPathPool != null) {
return fastPathPool.getConnection();
}
// Double‑checked locking
HikariPool result = pool;
if (result == null) {
synchronized (this) {
result = pool;
if (result == null) {
validate();
LOGGER.info("{} - Starting...", getPoolName());
try {
pool = result = new HikariPool(this);
this.seal();
} catch (PoolInitializationException pie) {
if (pie.getCause() instanceof SQLException) {
throw (SQLException) pie.getCause();
} else {
throw pie;
}
}
LOGGER.info("{} - Start completed.", getPoolName());
}
}
}
return result.getConnection();
}When the first request arrives, the method checks whether the pool is null. If it is, a new HikariPool is created inside a synchronized block. Two fields are involved:
fastPathPool – a final reference determined at construction time; it is null when the no‑arg constructor is used.
pool – a volatile reference that is lazily instantiated in getConnection() . Because it is volatile, every read/write goes to main memory, which is slightly slower than the non‑volatile fast path.
Pool Initialization
The constructor of HikariPool performs the full initialization of the pool’s internal structures.
public HikariPool(final HikariConfig config) {
super(config);
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);
}This method initializes the configuration, creates the ConcurrentBag for connections, sets up housekeeping tasks (e.g., leak detection, max‑lifetime eviction), and creates thread pools for adding and closing connections.
Monitoring
HikariCP provides built‑in metrics that can be exposed via the MetricsTrackerFactory interface. Custom implementations can be supplied during pool initialization.
public interface MetricsTrackerFactory {
/**
* Create an instance of an IMetricsTracker.
*
* @param poolName the name of the pool
* @param poolStats a PoolStats instance to use
* @return a IMetricsTracker implementation instance
*/
IMetricsTracker create(String poolName, PoolStats poolStats);
}Typical metric names (all prefixed with hikaricp ) include:
public static final String HIKARI_METRIC_NAME_PREFIX = "hikaricp";
private static final String METRIC_CATEGORY = "pool";
private static final String METRIC_NAME_WAIT = HIKARI_METRIC_NAME_PREFIX + ".connections.acquire";
private static final String METRIC_NAME_USAGE = HIKARI_METRIC_NAME_PREFIX + ".connections.usage";
private static final String METRIC_NAME_CONNECT = HIKARI_METRIC_NAME_PREFIX + ".connections.creation";
private static final String METRIC_NAME_TIMEOUT_RATE = HIKARI_METRIC_NAME_PREFIX + ".connections.timeout";
private static final String METRIC_NAME_TOTAL_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections";
private static final String METRIC_NAME_IDLE_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.idle";
private static final String METRIC_NAME_ACTIVE_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.active";
private static final String METRIC_NAME_PENDING_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.pending";
private static final String METRIC_NAME_MAX_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.max";
private static final String METRIC_NAME_MIN_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.min";Key metrics such as connections.pending , connections.acquire , connections.timeout , and connections.active should be monitored to detect pool saturation, long acquisition times, or timeout spikes.
Configuration Interpretation
When HikariDataSource validates the configuration during pool creation, illegal values are reset to defaults. Below are the most important settings and their recommended values.
Important Settings
connectionTimeout : maximum time (ms) to wait for a connection from the pool. Default 30 000 ms. Values below 250 ms are reset to 30 s. Keep the default unless you need faster failure detection.
idleTimeout : maximum time (ms) a connection may sit idle in the pool. Default 600 000 ms. If idleTimeout + 1 s > maxLifetime and maxLifetime > 0 , it is reset to 0 (never expires). Values below 10 s are reset to 10 s.
maxLifetime : maximum lifetime (ms) of a connection in the pool. Default 1 800 000 ms (30 min). Values less than 30 s are reset to 30 min. It should be slightly lower than the database wait_timeout to avoid using expired connections.
minimumIdle : minimum number of idle connections the pool tries to maintain. Default 10. If the value is negative or greater than maximumPoolSize , it is reset to maximumPoolSize .
maximumPoolSize : maximum total connections (idle + in‑use). Default 10. Values less than 1 are reset to the default pool size. It is advisable to set this a few times higher than the expected peak of active connections, respecting the database’s own connection limit.
Summary
The initialization of HikariCP is deferred until the first connection request, at which point the pool and all its internal structures are created.
Key monitoring metrics (pending, acquire time, timeout, active connections, etc.) allow operators to keep the pool’s health in view and react to performance issues.
Default configuration works for most scenarios; however, tuning connectionTimeout , idleTimeout , maxLifetime , minimumIdle and maximumPoolSize based on actual load ensures the pool does not become a bottleneck.
政采云技术
ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining 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.