# Advanced Connection Pool Design with HikariCP

> HikariCP pool size should follow sustainable database concurrency rather than application request concurrency; a larger pool can increase contention without increasing throughput.

- Author: Muhammet Ali Köker
- Language: en
- Canonical: https://alikoker.com.tr/en/advanced-connection-pool-design-with-hikaricp
- Translation: https://alikoker.com.tr/hikaricp-ile-ileri-duzey-havuz-tasarimi
- Published: 2026-08-03T12:00:00+03:00
- Modified: 2026-09-08T02:30:00+03:00
- Verified: 2026-09-08T02:30:00+03:00
- Type: article

## Pool Size Is Not a Copy of Request Concurrency

Hundreds of concurrent application requests do not imply a need for hundreds of active JDBC connections. A connection pool should feed the database with a bounded number of reusable sessions that match sustainable database concurrency, not assign one permanent connection per request. Oversizing the pool can simply move the queue into the database and increase CPU, lock/latch, and I/O contention.

`maximumPoolSize` should therefore be derived from measured query service time, database CPU capacity, active-session cost, and acceptable queueing latency rather than from thread count alone. Pool saturation is not automatically a defect; with bounded timeouts and [backpressure](/en/wiki/backpressure), it can act as an explicit control point that protects the database from overload.

Increasing HikariCP `maximumPoolSize` does not add capacity to the database. A pool amortizes connection establishment and maps application concurrency onto a bounded number of physical sessions; if the database can sustain only a certain amount of concurrent work, a larger pool moves the queue rather than eliminating it.

With many Oracle `DataSource` instances, this becomes a capacity calculation rather than a tuning preference. When dozens of independent data sources each own a pool, the theoretical connection ceiling is the sum of their `maximumPoolSize` values; replicating that topology multiplies the ceiling again. I therefore derive pool size from Oracle session limits, RAC distribution, query service time, and acceptable queueing delay rather than from application thread count.

This distinction becomes more apparent in systems with many data sources. When dozens of separate `DataSource` instances each own a pool of the same size, the theoretical upper bound is the sum across all pools. Representing the same data sources with multiple copies increases that ceiling with the copy count. Oracle `PROCESSES`/`SESSIONS`, RAC service distribution, network sockets and operating-system resources must therefore be evaluated against the aggregate budget before application traffic is considered.

While tuning connection pools in high-traffic Java and Oracle systems, I observed that increasing the pool does not automatically reduce latency; once database concurrency limits are exceeded, waiting and contention are merely moved to another layer. The approach below therefore derives pool size from sustainable database service capacity rather than from application thread count.

## Determining pool size

Pool size cannot be derived from the hourly request count. A system can receive millions of requests per hour while requiring only a few simultaneous connections. The decisive factors are how long a connection remains occupied and how much of that occupancy occurs concurrently.

An initial estimate can be built with [Little's Law](/en/wiki/little-s-law):

```text
L = λW
```

`L` is the expected number of connections occupied at the same time. `λ` is the number of database operations per second. `W` is the average period for which one operation holds a connection.

For a system that performs 500 database operations per second and holds a connection for an average of 20 ms, average concurrency is:

```text
L = 500 x 0.020 = 10
```

This result does not directly mean `maximumPoolSize=10`. An average does not show bursts or the long tail of the queue. P95 and P99 operation times, query distribution, transaction duration, and traffic spikes must also be measured.

HikariCP documentation also emphasizes that a larger pool does not automatically produce higher performance. Connections beyond the database's CPU, disk, and query-execution capacity wait for resources instead of doing useful work. It is therefore unsurprising that latency can fall substantially under some workloads when the pool is made smaller.

Practical sizing relies on the following measurements:

- Time distribution of active connection count
- Idle connection count
- Number of threads waiting for a connection
- `getConnection()` wait time
- P95 and P99 transaction and query durations
- Oracle session and process limits
- Service distribution across RAC nodes
- Behavior of database CPU and disk queues as load rises

When a pool remains full, the solution is not always to enlarge it. The first question is why connections are held for so long. Slow SQL, excessively broad transaction boundaries, keeping a connection open during a network operation, delayed consumption of a result set, and [ORM](/en/wiki/object-relational-mapping)-induced N+1 queries can all produce the same symptom.

## Minimum connections and dynamic growth

`minimumIdle` defines the minimum number of idle connections retained by the pool. If `minimumIdle` is not specified, HikariCP treats it as equal to `maximumPoolSize` by default. The pool then behaves close to a fixed-size pool. HikariCP documentation regards a fixed-size pool as an appropriate default in many cases because it avoids connection-creation latency during sudden load.

This choice is not correct for every system. A fixed pool can waste a substantial number of connections when many data sources are used only occasionally. If only a small portion of a large set of Oracle schemas is active at any one time, opening every pool at full capacity during startup is unnecessary. A low `minimumIdle`, a measured `maximumPoolSize`, and controlled connection creation can be more suitable in such systems.

The cost of dynamic growth is connection establishment latency on the first requests. An Oracle connection involves more than opening a TCP socket. Listener routing, the Oracle Net negotiation, authentication, session creation, and any TLS or advanced security steps must complete. If several pools grow simultaneously under load, they can create a connection storm.

`idleTimeout` is meaningful only when `minimumIdle < maximumPoolSize`. The pool does not reduce idle connections below `minimumIdle`. Nor is an idle connection guaranteed to close at the exact configured millisecond. Timing varies according to the housekeeper scan.

## Timeout layers

A HikariCP system does not have a single timeout. Several timeouts that limit different failures must be designed together.

`connectionTimeout` defines how long application code waits to obtain a connection from the pool. If the pool is full and this period expires, an `SQLException` is raised. This value should be shorter than the total time budget of the user request. Otherwise, a thread can continue waiting for a connection even after the HTTP request or upper-layer operation has already ended.

`validationTimeout` is the time allocated to verify that a connection is alive. It must be less than `connectionTimeout`. By itself, it may not always prevent a validation query or `Connection.isValid()` call from hanging on the network for an extended period. Socket and network timeouts must also be configured in the JDBC driver.

`maxLifetime` limits how long a physical connection can remain in the pool. It should be shorter than the interval after which the database, firewall, NAT device, load balancer, or other network equipment closes the connection. The aim is for HikariCP to renew the connection in a controlled manner before an external layer silently kills it.

`keepaliveTime` helps keep a long-idle connection alive along the network path. It must be less than `maxLifetime`. Keepalive does not run on a connection currently borrowed by the application. It operates only on idle connections under pool control.

Short values such as `connectionTimeout=2000` and `validationTimeout=1000` can be a reasonable starting point for a low-latency internal system. They cannot be used as universal settings without measurement. If an RAC node transition, a remote network, a busy listener, or a security negotiation can exceed two seconds, the application will generate unnecessary connection failures.

The timeout chain should preserve this order:

JDBC query timeout
< transaction timeout
< application request timeout

Connection acquisition should consume only a portion of this total budget. If a database call can run for 30 seconds, setting `connectionTimeout` to 30 seconds allows queues to remain in memory for a long time during overload.

## Oracle network failures and rapid recovery

HikariCP can manage only connections under its control. After the application borrows a connection, HikariCP cannot forcibly recover it if the connection becomes stuck in an Oracle JDBC call. During a network partition, a call can remain blocked until the operating system times out while waiting for a response to a transmitted TCP packet. HikariCP's rapid-recovery guidance therefore considers driver-level socket timeouts necessary.

Oracle JDBC exposes different limits for connection establishment and reading. `CONNECT_TIMEOUT`, `TRANSPORT_CONNECT_TIMEOUT`, `oracle.net.CONNECT_TIMEOUT`, `oracle.jdbc.ReadTimeout`, and `Connection.setNetworkTimeout()` do not govern the same stage. Their meaning must be verified against the driver version and the structure of the connection URL. Oracle notes that a connection timeout can be applied separately to every address or IP in an `ADDRESS` list.

[Retry](/en/wiki/retry) settings require greater care in RAC and SCAN environments. One SCAN name can resolve to multiple IP addresses and listener paths. If a long connection timeout applies to every address, total failure time can become much longer than expected.

Blind retries should not be implemented at the pool layer. If it is unknown whether a transaction reached the server, repeating the same write can create duplicate records. A retry is appropriate only when the operation is idempotent or the transaction outcome can be determined reliably.

## Transaction boundaries and connection state

When a connection is returned, HikariCP attempts to reset selected JDBC states. These include `autoCommit`, `readOnly`, transaction isolation, catalog, and certain other properties. If a transaction remains open, a rollback can be issued while the connection is returned. Open `Statement` objects are also tracked and closed by the proxy layer.

These protections do not make a defective transaction design safe. A connection should be held only while SQL is being executed. An HTTP call, file access, artificial-intelligence inference, or long CPU operation should not run inside a transaction. Even if these operations do not use the database, they continue to consume pool capacity.

The `spring.jpa.open-in-view=false` setting is important for preventing the [persistence context](/en/wiki/persistence-context) and connection usage from unintentionally extending across the entire web request. Transaction boundaries should be defined explicitly in the service layer. Lazy-loading requirements should be addressed through query planning, DTO projection, or an explicit fetch strategy.

The isolation level should not be changed with an SQL command. HikariCP can reliably detect the change only through the JDBC `Connection.setTransactionIsolation()` call. An isolation level changed through SQL may not be reset when the connection returns to the pool and can leak to the next user.

When `autoCommit=false` is used, successful operations require an explicit `commit`, and failure paths require an explicit `rollback`. Spring transaction management can perform these actions. Mixing framework-managed transactions with manual `commit()` or `rollback()` calls on the same connection makes transaction state ambiguous.

## The boundary of virtual threads

A virtual thread can reduce the number of platform threads occupied while waiting for a JDBC connection. It does not increase the number of database connections. If tens of thousands of virtual threads call `getConnection()` simultaneously, the pool still provides only `maximumPoolSize` connections. All remaining calls wait in the pool queue.

A pool therefore becomes a natural concurrency limiter in systems that use virtual threads. An unlimited number of waiting virtual threads is not, however, an acceptable backpressure mechanism. Waiting tasks retain state on the heap, may continue unnecessary work after a request timeout, and can create a large wake-up wave after a failure clears.

The upper layer requires a bounded queue, semaphore, [admission control](/en/wiki/admission-control), or [load shedding](/en/wiki/load-shedding). The HikariCP queue should be the final line of defense. If the database can process 20 concurrent transactions, allowing 20,000 tasks to wait in front of the pool does not make the system more resilient.

HikariCP's housekeeper, connection-adder, and similar internal tasks can run on platform threads. Converting them to virtual threads is not an expected optimization. The actual benefit is that application request threads need not consume platform threads while blocked in JDBC calls.

## Leak detection and pool locking

`leakDetectionThreshold` produces a diagnostic stack trace when a connection remains in application code longer than the configured period. It does not reclaim the connection, terminate the transaction, or fix the leak. It only reports where the connection was acquired.

An excessively low threshold reports normal long-running transactions as leaks. In a busy production system, stack-trace generation and log volume create additional cost. A value as low as 2,000 ms can be useful during diagnosis, but it should not remain enabled continuously in a production system where some queries normally take several seconds. The threshold should be above the normal P99 transaction duration or disabled after the problem is resolved.

With a real connection leak, the `active` connection count rises and does not fall. With a slow query, connections eventually return to the pool. A time series distinguishes these two cases.

Pool-locking risk arises when one thread acquires more than one connection at the same time. If each of `Tn` threads can hold at most `Cm` connections, the stated lower bound for avoiding deadlock is:

```text
poolSize = Tn x (Cm - 1) + 1
```

When HikariCP documentation presents this formula, it also explains that the primary solution is not to enlarge the pool but to eliminate workflows that acquire multiple connections at once.

## Observability and failure diagnosis

A HikariCP configuration cannot be validated by reading the configuration dump in a log file. Pool behavior must be observed as a time series. At minimum, the following measurements are required:

- `active`
- `idle`
- `pending`
- `max`
- Connection acquisition time
- Connection usage time
- Connection creation time
- Timeout count

When `active=max` and `pending>0` persist, the pool is exhausted. If database CPU remains low, connections may be held unnecessarily long in the application layer. If database CPU and disk waits are high, enlarging the pool can make the problem worse.

Waits in `HikariPool.getConnection()` in a thread dump indicate pool scarcity. Accumulation in `OracleStatement.execute`, `T4C...`, `TimeoutSocketChannel.read`, or `NIOPacket` calls points more toward Oracle execution or the network layer than the pool. Heavy waits in `HouseKeeper`, `KeepaliveTask`, or `isConnectionAlive` can indicate delayed validation calls or many pools performing maintenance simultaneously.

A single thread dump is insufficient. Consecutive dumps, [Java Flight Recorder](/en/wiki/java-flight-recorder), a wall-clock profiler, and Oracle [AWR](/en/wiki/automatic-workload-repository) or ASH data should be compared over the same interval. A CPU profile shows only executing code. Threads waiting on JDBC sockets consume little CPU and can appear insignificant in CPU profiles.

In systems with many `DataSource` instances, metrics should be labeled by pool name, schema, and service information. Label counts must remain controlled to avoid high cardinality. Using every SQL statement or user identity as a metric label is inappropriate.

## Life cycle and configuration discipline

When JDBC or JPA starters are used, Spring Boot can select HikariCP as the default pool. When multiple `DataSource` instances are declared manually, the bean to which auto-configuration applies must be checked explicitly. Defining a custom `DataSource` bean can disable some auto-configuration behavior.

Every `HikariDataSource` must be closed with `close()` when the application shuts down. Otherwise, housekeeper threads and physical connections can leak between hot deployments or test runs. HikariCP states this life-cycle requirement explicitly.

`initializationFailTimeout` forms part of the policy that determines whether the application starts while the database is unavailable. A critical service that cannot perform any work without its database may prefer fail-fast behavior. In a system with several independent data sources, the pools can be managed separately if the failure of one schema should not stop the entire application. This is a service contract, not merely a technical default.

HikariCP does not maintain a `PreparedStatement` cache at the pool layer. When caching is required, the JDBC driver's per-connection mechanism or a server-side mechanism should be used. A general pool-level statement cache can create unexpected memory cost because the same SQL carries separate execution state for every connection. HikariCP documentation likewise leaves prepared-statement caching to the driver.

HikariCP's low latency comes from internal optimizations such as `ConcurrentBag`, fast access paths, proxy objects, and [JIT](/en/wiki/just-in-time-compilation)-friendly code structure. The project states that some hot methods are arranged at the bytecode level to remain within JIT inlining limits. These details are not pool settings. They do not compensate for slow SQL or incorrect transaction boundaries in the application.

A correct production approach to HikariCP is not a copied set of fixed settings. Pool count, connection limits, Oracle session capacity, transaction duration, network timeouts, and upper-layer concurrency must be modeled together. `maximumPoolSize=10`, `minimumIdle=2`, `connectionTimeout=2000`, `validationTimeout=1000`, and `autoCommit=false` can be a useful starting point in some systems. They become an actual configuration only after load tests, soak tests, and production metrics validate them.

A well-sized pool does not hide saturation. It bounds connection wait time, prevents more concurrent work from entering the database than it can sustain, and keeps the location of waiting visible during failure. HikariCP's value is not that it creates more connections, but that it enforces this boundary with low overhead.

## Relationship to database systems

Connection-pool capacity forms a concurrency boundary between application and database. The general transaction, integrity and query model is covered in [Database and Data Systems](/en/database-and-data-systems), while this article focuses on pool behavior.

## Relation to Routing

Selecting among multiple pools and deciding how large those pools should be are separate problems. [Dynamic Data Source Routing with HikariCP](/en/dynamic-data-source-routing-with-hikaricp) covers the `AbstractRoutingDataSource` boundary; this page focuses on aggregate physical-session budgeting.

## Pool Size Is a Queueing Decision, Not a Capacity Guess

Increasing a connection pool can look like increasing concurrency, but if the database can only execute a bounded amount of work efficiently, the larger pool merely moves the waiting line from the application into the database. Pool size should therefore be derived from service time, useful database concurrency, and acceptable queueing delay rather than from thread count alone.

[Connection Pool Exhaustion](/en/wiki/connection-pool-exhaustion), [Queueing Delay](/en/wiki/queueing-delay), and [Service Time](/en/wiki/service-time) separate those effects into observable quantities. In a latency-sensitive system, a healthy median can coexist with a collapsing tail; P99 and timeout distribution are often the earlier warning.

## References

- Brett Wooldridge et al. (n.d.). HikariCP - High-performance JDBC connection pool. HikariCP project. [URL](https://github.com/brettwooldridge/HikariCP)

- John D. C. Little. (1961). A Proof for the Queuing Formula: L = λW. Operations Research, 9(3), 383-387. [doi:10.1287/opre.9.3.383](https://doi.org/10.1287/opre.9.3.383)

- Ron Pressler; Alan Bateman. (2023). JEP 444: [Virtual Threads](/en/wiki/virtual-thread). OpenJDK. [URL](https://openjdk.org/jeps/444)

## Cite This Work

Köker, M. A. (2026). Advanced Connection Pool Design with HikariCP. alikoker.com.tr. https://alikoker.com.tr/en/advanced-connection-pool-design-with-hikaricp

- BibTeX: https://alikoker.com.tr/en/advanced-connection-pool-design-with-hikaricp.bib
- RIS: https://alikoker.com.tr/en/advanced-connection-pool-design-with-hikaricp.ris
- CSL-JSON: https://alikoker.com.tr/en/advanced-connection-pool-design-with-hikaricp.csl.json
