Have you ever run a load test only to suddenly see a flood of Connection is not available, request timed out errors? In most cases, the root cause is shipping HikariCP to production with its default settings.
Spring Boot uses HikariCP by default, but its default values are conservative settings that prioritize “just working.” Production traffic always requires tuning. In this article, we’ll go through the meaning of the key parameters and how to derive appropriate values in order.
For broader Spring Boot performance considerations that underpin HikariCP, Spring Data JPA Performance Optimization is also a useful reference. If you’re running multiple Pods on Kubernetes, consider connection count design alongside How to Deploy Spring Boot Apps to Kubernetes.
Why HikariCP?
Before Spring Boot 2.0, the default connection pool was Tomcat JDBC. The switch to HikariCP in 2.0 was primarily driven by its overwhelming speed in benchmarks and the lightness of its code. Its internal design minimizes locking, allowing it to maintain low latency even in high-throughput environments.
That said, “fast” doesn’t mean “configuration-free.” It’s better to approach it with the assumption that the defaults are not production-ready.
How Connection Pool Exhaustion Happens
Creating a DB connection is surprisingly expensive. The sequence of establishing a TCP connection, going through authentication, and initializing a session can be slower than the SQL execution itself. A connection pool creates those connections ahead of time and lends them out when requests arrive.
The problem is, when lent-out connections aren’t returned quickly — that is, when there are slow queries or connection leaks — the pool empties out. New requests are made to wait for the duration of connectionTimeout, and once that’s exceeded, they fail with a timeout exception. This is “DB connection exhaustion.”
Understanding the Key Parameters
maximumPoolSize: Default and Recommended Values
Default: 10. This is applied when you don’t explicitly configure it from Spring Boot. Recommended: Start with (core_count × 2) + 1, then adjust to around 10–30 based on Tomcat’s max-threads and the DB’s max_connections. The property name is spring.datasource.hikari.maximum-pool-size.
minimumIdle: Default and Recommended Values
Default: Same value as maximumPoolSize. Recommended: Set equal to maximumPoolSize and operate as a fixed-size pool. The HikariCP project explicitly recommends “not varying it dynamically.” The property name is spring.datasource.hikari.minimum-idle.
connectionTimeout: Default and Recommended Values
Default: 30000ms (30 seconds). Recommended: 3000–5000ms. 30 seconds is almost always too long in production and is a cause of long-blocking threads. The property name is spring.datasource.hikari.connection-timeout.
idleTimeout: Default and Recommended Values
Default: 600000ms (10 minutes). Recommended: The default is fine in most cases. It’s ignored when minimumIdle == maximumPoolSize. The property name is spring.datasource.hikari.idle-timeout.
maxLifetime: Default and Recommended Values
Default: 1800000ms (30 minutes). Recommended: Set it several tens of seconds shorter than the DB’s wait_timeout. MySQL’s default wait_timeout is 8 hours, against which 30 minutes is sufficiently short, so the default is usually fine. The property name is spring.datasource.hikari.max-lifetime.
keepaliveTime: Default and Recommended Values
Default: 0 (disabled). By default, no liveness check is performed. Recommended: In environments where firewalls cut TCP sessions, set 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.
There are many configurable parameters, but understanding the following six covers most cases.
| Parameter | Default | Role |
|---|---|---|
maximumPoolSize | 10 | Maximum number of connections the pool holds |
minimumIdle | Same as maximumPoolSize | Minimum connections to keep when idle |
connectionTimeout | 30000ms | Max wait time to acquire a connection |
idleTimeout | 600000ms | Time before discarding an idle connection |
maxLifetime | 1800000ms | Maximum lifetime of a connection |
keepaliveTime | 0 (disabled) | Liveness check interval |
HikariCP officially states that “we recommend setting minimumIdle to the same value as maximumPoolSize and using it as a fixed-size pool.” Varying the idle connection count complicates management.
How to Properly Size maximumPoolSize
It’s tempting to think “just make it big and we’re safe,” but this can backfire. The HikariCP official wiki “About Pool Sizing” gives the following formula.
connections = (core_count * 2) + effective_spindle_count
effective_spindle_count is the number of rotating disks; for SSDs or RDS, it’s generally treated as 1. For example, with a 4-core app server, 4 * 2 + 1 = 9, so around 10 is your starting point.
That said, this is just a guideline. In practice, you base it on your application’s concurrent thread count. Tomcat’s default max thread count is 200, but not every thread accesses the DB simultaneously. The realistic approach is to measure and adjust while watching pool utilization (hikaricp.connections.active).
One more thing not to forget is the DB-side max_connections. If you run multiple app servers, exceeding the DB’s limit with instance_count × maximumPoolSize will cause the DB to refuse connections. Design with a bird’s-eye view of total connection counts.
Outages Caused by Misconfigured connectionTimeout and idleTimeout
When connectionTimeout is too short, even legitimate requests immediately time out under heavy load. Conversely, when it’s too long, threads block for extended periods, jamming up the entire server. The default 30 seconds is too long in almost all cases, so narrowing it to around 3–5 seconds is recommended.
For idleTimeout and maxLifetime, you need to be careful of the relationship with the DB’s connection timeout setting (in MySQL, wait_timeout, defaulting to 8 hours). If maxLifetime is longer than the DB’s wait_timeout, HikariCP will still hold connections that the DB has already disconnected, and using them later will throw a SQLException.
As a guideline, set maxLifetime several tens of seconds shorter than the DB’s wait_timeout. For RDS, since wait_timeout is 8 hours, the default 30 minutes is usually fine, but in environments where the firewall has a short TCP timeout, combine it with keepaliveTime.
Example application.yml Configuration
The minimum configuration is just this. At the very least, review maximumPoolSize and connectionTimeout.
# Minimum configuration
spring:
datasource:
hikari:
maximum-pool-size: 20
connection-timeout: 3000
Here’s the recommended configuration for production. It’s convenient to allow overrides via environment variables.
# Recommended configuration
spring:
datasource:
url: ${DB_URL}
username: ${DB_USER}
password: ${DB_PASSWORD}
hikari:
# Computed from 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 load
connection-timeout: 3000
# Shorter than the DB's wait_timeout (e.g., 28800s)
max-lifetime: 1800000
# Discard idle connections after 10 minutes
idle-timeout: 600000
# Enable in firewalled environments to detect disconnects
keepalive-time: 60000
pool-name: MyAppPool
The configuration keys are the same in both Spring Boot 2.x and 3.x: spring.datasource.hikari.*.
Before/After Comparison
Here’s an illustrative comparison under a load scenario of 100 concurrent connections, of which 20% are slow queries (average 2 seconds).
| Configuration | maximumPoolSize | connectionTimeout | Timeout Error Rate |
|---|---|---|---|
| Default | 10 | 30000ms | ~40% |
| Tuned | 20 | 3000ms | ~3% |
| Over-provisioned | 100 | 3000ms | ~8% (throughput degrades) |
Over-provisioning increases thread contention and context switches on the DB side, paradoxically reducing throughput. “Bigger is safer” is not true — and this is also HikariCP’s official position.
Monitoring Pool Usage with Actuator
Common Errors and Troubleshooting
The typical errors around HikariCP have a limited set of root cause patterns.
Connection is not available, request timed out after Nms
Occurs when a connection couldn’t be acquired from the pool within connectionTimeout. There are three main causes.
- The pool size is insufficient: If
hikaricp.connections.activeis constantly pegged atmaximumPoolSize, the pool is too small. - Slow queries are hogging connections: Check the
hikaricp.connections.usage(borrow time) metric and cross-reference with slow query logs. - Connection leaks: The application may not be returning connections. The
leakDetectionThresholddescribed below can detect this.
Detecting Connection Leaks: leakDetectionThreshold
If you set leakDetectionThreshold, HikariCP emits a warning log when a borrowed connection isn’t returned within the specified time. Since it has some performance impact, it’s recommended to use it in staging rather than production.
spring:
datasource:
hikari:
# Warn for connections not returned within 60 seconds
leak-detection-threshold: 60000
The warning log includes the stack trace of the leaking code, making it easier to identify the root cause.
HikariPool-1 - Failed to validate connection
Happens often when maxLifetime is longer than the DB’s wait_timeout. HikariCP tries to reuse a connection that the DB has already disconnected, and fails. Set maxLifetime to be shorter than the DB’s wait_timeout.
After tuning, verify how the pool actually behaves. Adding spring-boot-actuator and micrometer-core to your dependencies exposes HikariCP metrics.
<!-- 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
/actuator/metrics/hikaricp.connections.active shows the current number of connections in use, and hikaricp.connections.pending shows the number of threads waiting. If you’re constantly seeing waiting threads, that’s a sign to increase maximumPoolSize. For detailed visualization with Prometheus and Grafana, see Spring Boot Observability Setup (Micrometer + Prometheus + Grafana).
Configuration Review Checklist
When reviewing your own project’s settings, run through the following.
- Is
maximumPoolSizestill at the default (10) in production? - Is
maxLifetimelonger than the DB’swait_timeout? - Is
connectionTimeoutstill at 30000ms (the default)? - Is the balance between Tomcat’s
max-threadsandmaximumPoolSizereasonable? - In multi-instance setups, does
instance_count × poolSizeexceed the DB’smax_connections?
Summary
HikariCP’s defaults are sufficient for development, but production traffic always requires a review. Start by adjusting maximumPoolSize and connectionTimeout to fit reality, then refine gradually while watching metrics in Actuator — that’s the safe path.
For JPA query optimization, see Spring Data JPA Performance Optimization. For combining with Virtual Threads, see Java 21 Virtual Threads and Spring Boot. Once your connection pool settings have stabilized, tidying up transaction management next will make incident triage much easier.