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

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.

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.

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.

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.

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.

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.

ParameterDefaultRole
maximumPoolSize10Maximum number of connections the pool holds
minimumIdleSame as maximumPoolSizeMinimum connections to keep when idle
connectionTimeout30000msMax wait time to acquire a connection
idleTimeout600000msTime before discarding an idle connection
maxLifetime1800000msMaximum lifetime of a connection
keepaliveTime0 (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).

ConfigurationmaximumPoolSizeconnectionTimeoutTimeout Error Rate
Default1030000ms~40%
Tuned203000ms~3%
Over-provisioned1003000ms~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.

  1. The pool size is insufficient: If hikaricp.connections.active is constantly pegged at maximumPoolSize, the pool is too small.
  2. Slow queries are hogging connections: Check the hikaricp.connections.usage (borrow time) metric and cross-reference with slow query logs.
  3. Connection leaks: The application may not be returning connections. The leakDetectionThreshold described 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 maximumPoolSize still at the default (10) in production?
  • Is maxLifetime longer than the DB’s wait_timeout?
  • Is connectionTimeout still at 30000ms (the default)?
  • Is the balance between Tomcat’s max-threads and maximumPoolSize reasonable?
  • In multi-instance setups, does instance_count × poolSize exceed the DB’s max_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.