HikariCP とは → HikariCP: Connection Pool Tuning Guide

Here’s the translated article body:


Have you ever run a load test and suddenly seen a flood of Connection is not available, request timed out errors? In most cases, the root cause is deploying HikariCP to production with its default settings left untouched. The default values are conservative, prioritizing “it just works” over production traffic, so tuning is required. In this article, we’ll walk through what the key parameters mean and how to derive appropriate values for them.

For query-side improvements, see JPA Performance Optimization in Spring Data, and for connection count design when running multiple Pods, see How to Deploy a Spring Boot App to Kubernetes.

If you just want a working configuration example, jump straight to Configuration Example for application.yml.

What Is HikariCP

HikariCP is a JDBC connection pool implementation for Java. The name comes from the Japanese word for “light” (光), reflecting its speed and lightweight design. It has been the default since Spring Boot 2.0, and adding spring-boot-starter-jdbc or spring-boot-starter-data-jpa as a dependency transitively pulls in com.zaxxer:HikariCP.

A connection pool is a mechanism that creates and holds a fixed number of DB connections in advance, then lends them out to the application for reuse. This not only avoids the cost of redoing TCP connections and authentication on every request, but also controls the upper limit on the number of connections the app opens to the DB. It’s worth noting that in HikariCP, Connection.close() doesn’t mean a physical disconnection — it means “returning the connection to the pool.” Keeping this in mind will make the discussion of leaks below easier to understand.

If a connection lent out due to a slow query or connection leak never comes back, the pool empties out, and new requests will wait for connectionTimeout before throwing a timeout exception. This is the true cause of the Connection is not available error mentioned at the start — what’s commonly called “DB connection exhaustion.”

Spring Boot Auto-Configures It

In Spring Boot, simply specifying spring.datasource.url / username / password auto-configures a HikariDataSource bean, and you can tune the pool’s behavior via spring.datasource.hikari.* properties. You’ll need to add the JDBC driver (mysql-connector-j, postgresql, etc.) as a separate dependency.

Using HikariCP in Plain Java Without Spring

HikariCP itself doesn’t depend on Spring, so it can also be used from standalone Java applications or batch jobs.

<!-- pom.xml (when using it outside Spring Boot, specify the version explicitly. Check Maven Central for the latest version) -->
<dependency>
  <groupId>com.zaxxer</groupId>
  <artifactId>HikariCP</artifactId>
  <version>6.3.0</version>
</dependency>
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/app");
config.setUsername("app");
config.setPassword("secret");
config.setMaximumPoolSize(20);
config.setConnectionTimeout(3000);
config.setPoolName("MyAppPool");

HikariDataSource dataSource = new HikariDataSource(config);

try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement("SELECT 1")) {
    ps.executeQuery();
} // close() returns the connection to the pool (not a physical disconnect)

// Close the entire pool when the app shuts down
dataSource.close();

HikariConfig setter names correspond one-to-one with Spring Boot property names (setMaximumPoolSize ⇔ maximum-pool-size), and the meaning of each parameter is the same regardless of which way you use it.

What Does “HikariPool-1” in the Startup Log Mean?

When you start a Spring Boot app, you’ll see log output like this:

com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
com.zaxxer.hikari.pool.HikariPool  : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@1a2b3c
com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.

HikariPool-1 is the auto-assigned pool name when pool-name isn’t specified; with multiple DataSources, the number increments to HikariPool-2, and so on. There are three log lines worth remembering.

  • Start completed. marks the completion of pool initialization. Since the pool is initialized the first time a connection is requested, depending on your configuration this may appear not right at startup, but when the first request comes in.
  • Exception during pool initialization. means not a single connection could be established. In most cases this is caused by an incorrect JDBC URL or credentials, or the DB not being up yet — not a pool size issue.
  • Shutdown initiated... is logged when the app shuts down. If in-flight requests fail right before this line, check the graceful shutdown configuration in How to Achieve Graceful Shutdown and Zero-Downtime Deployment in Spring Boot.

Understanding the Key Parameters

There are many configurable parameters, but understanding the following six will cover most cases.

Default: 10. Recommended: start from (core count × 2) + 1, then adjust to roughly 10–30 based on Tomcat’s max-threads and the DB’s max_connections. The property name is spring.datasource.hikari.maximum-pool-size.

Default: same value as maximumPoolSize. Recommended: keep it equal to maximumPoolSize and run it as a fixed-size pool. HikariCP’s official documentation explicitly recommends “not varying this dynamically.” The property name is spring.datasource.hikari.minimum-idle.

Default: 30000ms (30 seconds). Recommended: 3000–5000ms. The property name is spring.datasource.hikari.connection-timeout.

Default: 600000ms (10 minutes). Recommended: the default is usually fine. It’s ignored when minimumIdle == maximumPoolSize. The property name is spring.datasource.hikari.idle-timeout.

Default: 1800000ms (30 minutes). Recommended: set it several tens of seconds shorter than the DB’s wait_timeout. Since MySQL’s default wait_timeout is 8 hours, 30 minutes is already short enough, so the default is usually fine as-is. The property name is spring.datasource.hikari.max-lifetime.

Default: 0 (disabled). Recommended: in environments where firewalls terminate TCP sessions, set it to 30000–60000ms (30–60 seconds). It must be shorter than maxLifetime and at least 30 seconds. The property name is spring.datasource.hikari.keepalive-time.

How to Correctly Calculate maximumPoolSize

It’s tempting to think “just make it bigger to be safe,” but this can actually backfire. HikariCP’s official wiki page “About Pool Sizing” lists the following formula:

connections = (core_count * 2) + effective_spindle_count

effective_spindle_count refers to the number of rotating disks, and it’s common to treat this as 1 for SSDs or RDS. For 4 cores, that gives 4 * 2 + 1 = 9 — in other words, around 10 is a reasonable starting point.

There’s no need to match Tomcat’s max-threads. Even with 200 max threads against a pool of 20, HikariCP’s design philosophy is that it’s more efficient to let the app-side pool make requests wait briefly than to let them contend on the DB side. Adjust based on actual measurements of pool utilization (hikaricp.connections.active).

Another thing not to forget is the DB’s own max_connections. If instance count × maximumPoolSize (or the sum across all pools if you have multiple) exceeds the DB’s limit, connections will be rejected, so design with the full picture of total connection count in mind.

Note that enabling Java 21 virtual threads doesn’t change the DB’s processing capacity, so this formula still applies as-is. In fact, since a large number of virtual threads end up waiting on the pool, monitoring connectionTimeout and pending (discussed below) becomes even more important. See Java 21 Virtual Threads and Spring Boot for details.

Failures Caused by Misconfigured connectionTimeout and idleTimeout

If connectionTimeout is too short, even legitimate requests will time out immediately under high load. Conversely, if it’s too long, threads get blocked for extended periods and the whole server backs up. The default of 30 seconds is too long for most use cases, so narrowing it to around 3–5 seconds is recommended.

If maxLifetime is set longer than the DB’s connection timeout setting (e.g., MySQL’s wait_timeout), HikariCP may continue holding onto a connection that the DB has already closed, resulting in a SQLException the next time it’s used. As a guideline, set it several tens of seconds shorter than the DB’s wait_timeout. In environments where firewalls impose short TCP timeouts, also use keepaliveTime in combination.

Configuration Example for application.yml

A minimal configuration looks like this. Start by reviewing just maximumPoolSize and connectionTimeout.

# Minimal configuration
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      connection-timeout: 3000

Here’s a recommended configuration for production.

# Recommended configuration
spring:
  datasource:
    url: ${DB_URL}
    username: ${DB_USER}
    password: ${DB_PASSWORD}
    hikari:
      # Calculated based on core count, thread count, and DB max_connections
      maximum-pool-size: ${HIKARI_MAX_POOL_SIZE:20}
      minimum-idle: ${HIKARI_MAX_POOL_SIZE:20}
      # Upper bound to avoid dragging down legitimate requests under high load
      connection-timeout: 3000
      # Set shorter than the DB's wait_timeout (e.g., 28800s)
      max-lifetime: 1800000
      # Discard idle connections after 10 minutes
      idle-timeout: 600000
      # Enable to detect disconnects in firewalled environments
      keepalive-time: 60000
      pool-name: MyAppPool

The configuration keys are the same (spring.datasource.hikari.*) in both Spring Boot 2.x and 3.x. Passing the password via environment variables, as in this example, is the first choice, but if you must write it into a config file, the ENC()-format encryption described in How to Encrypt Sensitive Config File Values Using Jasypt in Spring Boot is an easy option.

Before-and-After Comparison

Here’s an illustrative comparison under a load scenario with 100 concurrent connections, 20% of which are slow queries.

ConfigurationmaximumPoolSizeconnectionTimeoutTimeout Error Rate
Default1030000ms~40%
Tuned203000ms~3%
Oversized1003000ms~8% (throughput drops)

Note that oversizing the pool increases thread contention on the DB side, which can actually reduce throughput.

Monitoring Pool Usage with Actuator

After tuning, add spring-boot-starter-actuator as a dependency and check the actual behavior of the pool using the HikariCP metrics automatically exposed via Micrometer.

<!-- pom.xml -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# Enable the metrics endpoint
management:
  endpoints:
    web:
      exposure:
        include: metrics

You can check metrics by appending the metric name to the URL, e.g. /actuator/metrics/hikaricp.connections.active. Here are the ones worth watching in particular:

MetricMeaningHow to Interpret
hikaricp.connections.activeNumber of connections in useIf it’s constantly pinned at maximumPoolSize, the pool is undersized or there are slow queries
hikaricp.connections.idleNumber of idle connectionsIf it stays consistently high, the pool is oversized
hikaricp.connections.pendingNumber of threads waiting for a connectionIf this is consistently above 0, action is needed
hikaricp.connections.timeoutCumulative count of connection acquisition timeoutsIf this keeps increasing, connectionTimeout is being exceeded
hikaricp.connections.acquireTime taken to acquire a connectionIf p99 is in the hundreds of ms, waiting has become the norm
hikaricp.connections.usageConnection borrow durationThe longer this is, the more likely it’s a slow query or a leak

Persistent waiting (pending) is a signal to increase maximumPoolSize, but before doing so, check whether usage shows connections being held for a long time — i.e., whether the root cause is actually a slow query or a leak. If you have multiple DataSources, you can filter by the pool tag. For detailed visualization with Prometheus and Grafana, see Setting Up Observability in Spring Boot (Micrometer + Prometheus + Grafana).

Common Errors and Troubleshooting

Typical errors around HikariCP tend to fall into a limited set of causes.

Connection is not available, request timed out after Nms

This occurs when a connection can’t be acquired from the pool within the connectionTimeout window. There are three main causes.

  1. Pool size is insufficient: if hikaricp.connections.active is consistently pinned at maximumPoolSize, the pool is undersized.
  2. A slow query is holding a connection for a long time: check the hikaricp.connections.usage (borrow duration) metric and cross-reference it against slow queries in the SQL logs.
  3. Connection leak: the application may not be returning connections. This can be detected with leakDetectionThreshold, described below.

Detecting Connection Leaks with leakDetectionThreshold

Setting leakDetectionThreshold causes a warning log to be emitted if a lent-out connection isn’t returned within the specified time. Since this has a performance impact under load, it’s recommended to use it in a staging environment rather than production.

spring:
  datasource:
    hikari:
      # Warn about connections not returned within 60 seconds
      leak-detection-threshold: 60000

The warning log includes a stack trace of the leaking code, making it easier to identify the culprit.

HikariPool-1 - Failed to validate connection

This happens frequently when maxLifetime is set longer than the DB’s wait_timeout. It’s a pattern where HikariCP tries to reuse a connection the DB has already closed, and the attempt fails. Reconfigure maxLifetime to be shorter than the DB’s wait_timeout. In environments where firewalls or NAT devices close idle TCP sessions after a certain period, setting keepaliveTime to 30–60 seconds to proactively detect disconnects is also effective.

Other Settings Worth Knowing

Finally, here’s a summary of settings that often come up as questions.

validationTimeout and connectionTestQuery

Generally, no configuration is needed. Before lending out a connection, HikariCP checks its liveness using JDBC4’s Connection.isValid(), and the upper bound on that wait time is validationTimeout (default 5000ms). connectionTestQuery is a backward-compatibility option for older drivers that don’t support JDBC4, and the official documentation itself recommends “not setting this for drivers that support it.” If you do adjust validationTimeout, make sure it’s always shorter than connectionTimeout.

Relaxing Startup Ordering Constraints with initializationFailTimeout

The constraint that the app won’t start unless the DB is already up can be controlled via initializationFailTimeout (default 1ms). Setting it to 0 allows startup to continue even if the initial connection fails; a negative value skips the connection check at startup entirely. If you want to wait for a DB container to start, -1 can be used, but in production it’s safer to leave the default in place so configuration mistakes are caught early.

data-source-properties for MySQL

HikariCP itself doesn’t implement PreparedStatement caching by design, so caching is enabled on the JDBC driver side instead. The HikariCP official wiki’s recommended values for MySQL are as follows.

spring:
  datasource:
    hikari:
      data-source-properties:
        cachePrepStmts: true
        prepStmtCacheSize: 250
        prepStmtCacheSqlLimit: 2048
        useServerPrepStmts: true

Since these values are passed straight through to the driver, for PostgreSQL you’d specify driver-specific keys instead, such as reWriteBatchedInserts: true.

Separating Configuration Across Multiple DataSources

Use a separate @ConfigurationProperties prefix for each DataSource, and give each a distinct pool-name.

@Bean
@ConfigurationProperties("app.datasource.primary")
public DataSourceProperties primaryProperties() {
    return new DataSourceProperties();
}

@Bean
@ConfigurationProperties("app.datasource.primary.hikari")
public HikariDataSource primaryDataSource(DataSourceProperties primaryProperties) {
    return primaryProperties.initializeDataSourceBuilder()
            .type(HikariDataSource.class)
            .build();
}
app:
  datasource:
    primary:
      url: jdbc:mysql://primary:3306/app
      hikari:
        pool-name: PrimaryPool
        maximum-pool-size: 20
    replica:
      url: jdbc:mysql://replica:3306/app
      hikari:
        pool-name: ReplicaPool
        maximum-pool-size: 10

In this case, keep in mind that against the DB’s max_connections, you need to estimate based on the sum of maximumPoolSize across all pools, multiplied by the instance count.

Configuration Review Checklist

When reviewing your own project’s configuration, go through the following checklist.

  • Is maximumPoolSize still left at its default (10) in production?
  • Is maxLifetime set longer than the DB’s wait_timeout?
  • Is connectionTimeout still left at its default of 30000ms?
  • Is there a good balance between Tomcat’s max-threads and maximumPoolSize?
  • In a multi-instance setup, does instance count × poolSize exceed the DB’s max_connections?

Summary

HikariCP’s default settings are sufficient for development, but production traffic always requires a review. The safe approach is to first adjust maximumPoolSize and connectionTimeout to match your actual workload, then fine-tune incrementally while watching metrics via Actuator.

For JPA query optimization, also check out JPA Performance Optimization in Spring Data.

For a detailed monitoring setup, see the HikariCP Monitoring Guide.