Company
About Tomosu
Platform
Platform & Agents Indexes How it works Solutions Pricing
Get Started
MCP Server VS Code — Plugin Installation Scan Your Repo — Guide Integrations · GitHub App Integrations · CodeRabbit MCP FAQ
Free Tools
Governance Impact
Resources
Blogs News Download / Free Trial Book a call →
Production Debugging · Connection pools

HikariCP “Connection Is Not Available, Request Timed Out”: How to Find the Real Cause

Tomosu AI·14 min read·

The error message tells you the pool ran out of connections. It does not tell you why. Before you change a setting, you need to know whether the pool is leaking connections, holding them too long, deadlocking on itself, failing to create new ones, or just too small.

Quick answer

HikariCP throws SQLTransientConnectionException: Connection is not available, request timed out after 30000ms when a thread waits longer than connectionTimeout for a free connection. Every connection was busy and the pool could not add one in time.

Five different causes produce the same message:

Raising maximumPoolSize or connectionTimeout is the most common suggestion, and it is only the right fix for the last cause. This guide shows how to collect the evidence that tells the five causes apart. That evidence includes pool metrics, leak detection traces, thread dumps, and database session state, plus a map of the code paths that acquire and release connections.

What does “Connection is not available, request timed out” mean?

A HikariCP pool keeps a fixed maximum number of physical database connections (maximumPoolSize, default 10). When application code calls getConnection(), HikariCP hands over an idle connection. If none is idle and the pool is already at its maximum, the calling thread waits. If the wait exceeds connectionTimeout (default 30,000 ms), the call fails.

Typical exceptionsymptom
java.sql.SQLTransientConnectionException: HikariPool-1 -
  Connection is not available, request timed out after 30000ms (total=10, active=10, idle=0, waiting=4)
    at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java)
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java)
    at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java)

Recent HikariCP versions append the pool statistics to the message. Those four numbers are the first piece of evidence, and most people skip them.

WHAT THE TIMEOUT ACTUALLY MEASURES WAITING THREADS exec-11 waiting exec-12 waiting exec-13 waiting exec-14 waiting HIKARIPOOL-1 · MAXIMUMPOOLSIZE = 10 busybusybusybusybusy busybusybusybusybusy 10 of 10 connections in use · 0 idle WAIT UP TO connectionTimeout 30,000 ms by default THEN THROWS SQLTransient- ConnectionException HikariPool-1 - Connection is not available, request timed out after 30000ms (total=10, active=10, idle=0, waiting=4)
The timeout measures how long a thread waited for a connection. It says nothing about why the connections were busy.
FieldMeaningWhat it hints at
totalPhysical connections currently openIf total < maximumPoolSize while threads wait, the pool is failing to create connections
activeConnections borrowed by application codePinned at the maximum means every connection is out; the question is where
idleConnections sitting in the pool, readyShould be zero at the moment of a timeout; if not, look for a very short connectionTimeout
waitingThreads blocked in getConnection()How much demand is queued behind the busy connections
Check the chained cause

If HikariCP failed to create a connection recently, it attaches that failure as the exception’s cause. A Caused by: ... Connection refused, an authentication error, or too many connections from the database changes the diagnosis completely. Read the whole stack trace before reading the pool configuration.

Five causes, one error message

Developer questions about this error on Stack Overflow keep circling the same confusion. The pool is exhausted. Is it a leak, a long running query, or a pool that is simply too small? The honest answer is that the error cannot tell you. The evidence around it can.

WHICH CAUSE IS IT? FOUR QUESTIONS, IN ORDER Q1 · POOL STATS IN THE MESSAGE Is total below maximumPoolSize while threads wait? Q2 · ACTIVE CONNECTIONS OVER TIME Does active stay at the maximum after traffic drops? Q3 · CONNECTION USAGE TIME Are connections held far longer than a query takes? Q4 · THREAD DUMP Do threads holding a connection wait for another? Creation failure Read the chained cause, DB limits Connection leak A path never calls close() Long hold Slow SQL, long tx, remote call Pool deadlock Nested acquisition per thread YESYESYESYES NONONONO Undersized pool Hold times are healthy but throughput × hold time exceeds maximumPoolSize. Now a bigger pool is justified.
Ask the questions in order. Increasing the pool size is the right answer only when all four come back “no”.

1. Connection creation failure

The pool is not full, yet threads time out. HikariCP is trying to open new physical connections and failing or taking too long. Common reasons are the database hitting max_connections (often because several services or replicas share it), network or DNS problems, TLS handshakes that stall, or credentials that were rotated. The stats show total below the maximum, and the exception usually carries the underlying cause.

2. Connection leak

A code path borrows a connection and never returns it. Each leak permanently removes one connection from the pool, so the pool degrades step by step until it is empty. The signature is active climbing over hours or days and not coming back down when traffic falls. Restarting the service “fixes” it until the leak catches up again.

3. Long hold: slow queries, long transactions, and remote calls

Nothing is leaked, but each borrower keeps its connection for much longer than expected. A query that used to take 20 ms now takes 2 seconds because of a missing index, lock contention, or a larger table. A transaction wraps an HTTP call, a message publish, or file processing. The connection is returned eventually, so active drops when load drops. Under load it is pinned.

4. Pool deadlock from nested acquisition

A thread that already holds a connection asks the pool for a second one. Common culprits are Propagation.REQUIRES_NEW, a second DataSource.getConnection() inside a transaction, or a callback that opens its own session. With a pool of 10 and 10 concurrent requests that each hold one connection and wait for another, no thread can finish. They all wait until connectionTimeout.

5. Undersized pool

Every borrower behaves correctly and returns its connection quickly, but demand simply exceeds supply. This is the only cause where increasing maximumPoolSize is the fix, and it can only be confirmed after ruling out the other four.

Collect the evidence first

Each cause leaves different fingerprints. Gather these five sources before changing anything, ideally during or right after an incident.

Evidence 1: Pool metrics over time

A single snapshot cannot distinguish a leak from a traffic spike. You need active, idle, pending, and connection usage time as a time series. If you use Micrometer (Spring Boot does this automatically with Actuator), HikariCP publishes:

Without a metrics registry, turn on DEBUG logging for com.zaxxer.hikari. The housekeeper thread logs pool stats periodically (every 30 seconds by default), which is enough to see the trend:

DEBUG log · pool stats trend
HikariPool-1 - Before cleanup stats (total=10, active=4, idle=6, waiting=0)
HikariPool-1 - Before cleanup stats (total=10, active=7, idle=3, waiting=0)
HikariPool-1 - Before cleanup stats (total=10, active=10, idle=0, waiting=2)
HikariPool-1 - Before cleanup stats (total=10, active=10, idle=0, waiting=9)

Evidence 2: Leak detection stack traces

HikariCP can tell you who borrowed a connection that has been out too long. Leak detection is off by default. Enable it with a threshold above your longest legitimate hold time (the minimum is 2,000 ms):

Spring Boot · application.properties
# Warn when a connection is held longer than 20 s, with the borrower's stack trace
spring.datasource.hikari.leak-detection-threshold=20000

# Periodic pool stats in the logs
logging.level.com.zaxxer.hikari=DEBUG

# Expose HikariPoolMXBean over JMX (active, idle, threads awaiting connection)
spring.datasource.hikari.register-mbeans=true
Plain Java · HikariConfig
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setMaximumPoolSize(10);
config.setLeakDetectionThreshold(20_000); // ms; 0 = disabled (default)
config.setRegisterMbeans(true);

When the threshold is crossed, HikariCP logs the stack trace captured at the moment the connection was borrowed. That tells you exactly which code path took it:

WARN · ProxyLeakTask
Connection leak detection triggered for org.postgresql.jdbc.PgConnection@6b1d4f
  on thread http-nio-8080-exec-7, stack trace follows
java.lang.Exception: Apparent connection leak detected
    at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java)
    at com.example.report.ReportDao.export(ReportDao.java:42)
    at com.example.report.ReportService.nightlyExport(ReportService.java:88)

Now watch for what happens next. If the same connection is later returned, HikariCP logs a follow-up:

INFO · ProxyLeakTasknot a real leak
Previously reported leaked connection org.postgresql.jdbc.PgConnection@6b1d4f
  on thread http-nio-8080-exec-7 was returned to the pool (unleaked)
“Apparent” is the important word

Leak detection measures only how long a connection has been out. A warning followed by “unleaked” means the connection came back: that is a long hold, not a leak. A warning with no follow-up, repeating from the same stack trace, is a real leak.

Evidence 3: Thread dumps

A thread dump taken while threads are waiting separates the two populations you care about: threads waiting for a connection, and threads holding one.

Shell
# Take two or three dumps ~10 s apart during the incident
jcmd <pid> Thread.print > threads-1.txt

# How many threads are waiting on the pool?
grep -c "HikariPool.getConnection" threads-1.txt

Waiting threads are parked inside the pool:

Waiter
"http-nio-8080-exec-12" TIMED_WAITING (parking)
    at java.util.concurrent.locks.LockSupport.parkNanos
    at java.util.concurrent.SynchronousQueue.poll
    at com.zaxxer.hikari.util.ConcurrentBag.borrow
    at com.zaxxer.hikari.pool.HikariPool.getConnection
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin

The holders are more interesting. Look at what threads inside your data access code are doing:

Evidence 4: The database’s view of the sessions

The database sees every one of the pool’s connections and knows what each is doing. In PostgreSQL:

PostgreSQL · pg_stat_activity
SELECT pid, state, wait_event_type, wait_event,
       now() - xact_start  AS xact_age,
       now() - query_start AS query_age,
       left(query, 80)     AS last_query
FROM   pg_stat_activity
WHERE  datname = current_database()
  AND  backend_type = 'client backend'
ORDER BY xact_start NULLS LAST;
What you seeWhat it suggests
active with a large query_ageA slow query. Check the plan, indexes, and data growth.
active with wait_event_type = LockLock contention. Another transaction is blocking these sessions.
idle in transaction with a large xact_ageThe application opened a transaction and is doing something else while holding it, such as a remote call or slow processing, or it never committed.
idle sessions while the pool reports active=maxConnections are borrowed but unused, which is typical of a leak with autocommit on.
Fewer sessions than maximumPoolSize and connection errors in the logCreation failure. Check max_connections across every client of this database.

For MySQL, use SHOW FULL PROCESSLIST and information_schema.innodb_trx for the same picture. Setting the JDBC ApplicationName property makes the pool’s sessions easy to filter.

Evidence 5: The code paths themselves

The first four sources tell you what kind of problem you have. Only the code tells you where it is and how many other places share the same pattern. That gets its own section below.

Leak vs long running query vs undersized pool: how to tell them apart

This is the question most developers get stuck on. The pool looks the same in all three cases at the moment of the timeout. The difference shows up over time, and in where the connection time is spent.

HOW LONG IS ONE CONNECTION HELD? Healthy Slow query Remote call inside a transaction Leak SQL ~20 ms, returned SQL ~2.0 s (missing index, lock wait) HTTP call ~2.9 s: connection held, database idle Never returned: close() skipped on some path 0 s1 s2 s3 s Connection usage time (borrow to return) is the metric that separates these, not query time alone.
The same pool of 10 supports very different throughput depending on how long each connection is held.
SignalConnection leakLong holdPool deadlockUndersized pool
active after traffic dropsStays high, ratchets upFalls backFalls after timeouts fireFalls back
Connection usage timeUnbounded for leaked onesHigh (p95/p99)Up to connectionTimeoutNormal
Leak detectionWarnings, no “unleaked”Warnings, then “unleaked”Possible, then “unleaked”Quiet
Thread dumpNo owner for missing connectionsHolders in socket read or HTTP callHolders waiting in getConnectionHolders doing short, normal work
Database sessionsidle but borrowedactive or idle in transactionidle in transactionShort active queries
Correlates withUptime; a restart “fixes” itSpecific endpoints, data size, a slow dependencyConcurrency near pool sizeThroughput

The sizing arithmetic

The number of connections a service needs on average follows Little’s Law: throughput × average hold time. That is why a hold time problem and a capacity problem look identical from the outside.

CONNECTIONS BUSY = THROUGHPUT × HOLD TIME Throughput200 req/s × Hold time50 ms = Busy on average10 connections: pool full Throughput200 req/s × Hold time, slow dependency250 ms = Busy on average50 connections needed Both rows time out on a pool of 10. Only the first is a capacity problem; the second is a hold time problem.
Average is a floor. Bursts and variance mean you need headroom above it, but the ratio tells you which lever to pull.

The HikariCP project’s own pool sizing guidance argues for smaller pools than most teams expect. The database can only execute so many queries in parallel, and extra connections add contention rather than throughput. It also gives the minimum size that avoids pool deadlock when each thread needs several connections: Tn × (Cm − 1) + 1, where Tn is the maximum number of threads and Cm the maximum connections a single thread holds at once.

Map the code paths that acquire and release connections

Runtime evidence narrows the cause. It rarely hands you the complete list of code that has the problem. A leak detection trace shows the one path that tripped the threshold today, not the three other methods written the same way. To fix the class of problem rather than the instance, inventory the code paths that touch the pool:

  1. Acquisition points. Every DataSource.getConnection(), @Transactional boundary, TransactionTemplate, EntityManager or Session opened manually, and repository method that returns a Stream<T> or cursor.
  2. What happens while it is held. SQL only, or also HTTP calls, message publishing, file I/O, retries with backoff, sleep, or waiting on other threads?
  3. Release on every path. Is close() guaranteed through try-with-resources or a framework template, including exception and early return paths?
  4. Nested acquisition. Can a thread holding a connection request another through REQUIRES_NEW, a second data source call, or an event listener that opens its own transaction?
  5. Blast radius. Which endpoints, jobs, and consumers share this pool, and what fails for them when it starves?
ACQUIRE → HOLD → RELEASE, PER CODE PATH (ILLUSTRATIVE) CODE PATHACQUIRES VIAHELD DURINGRELEASE OrderService.placeOrder @Transactional entry SQL + paymentHTTP call Held across remote callCommit after HTTP returns ReportDao.export getConnection() Query + CSV mapping Leaks on exceptionclose() only on success AuditService.record REQUIRES_NEW Outer transactionstill open Two per threadDeadlock risk at pool size UserRepository.findActive JdbcTemplate Single short query Released correctlyTemplate closes it
One leak trace points at one row. The map shows every row, including those that have not failed yet.

Here are the two patterns behind most real incidents, and what the fix looks like.

Leak: close() skipped on the exception path

ReportDao.javaleaks on exception
public List<Row> export() throws SQLException {
    Connection conn = dataSource.getConnection();
    PreparedStatement ps = conn.prepareStatement(EXPORT_SQL);
    ResultSet rs = ps.executeQuery();
    List<Row> rows = toRows(rs);   // throws on a bad row: conn is never closed
    conn.close();
    return rows;
}
ReportDao.javareleased on every path
public List<Row> export() throws SQLException {
    try (Connection conn = dataSource.getConnection();
         PreparedStatement ps = conn.prepareStatement(EXPORT_SQL);
         ResultSet rs = ps.executeQuery()) {
        return toRows(rs);          // closed even if toRows throws
    }
}

The same pattern appears in less obvious forms. Examples include a Spring Data repository method returning Stream<T> that the caller never closes, DataSourceUtils.getConnection() without a matching releaseConnection(), and a connection passed to another thread or stored in a field.

Long hold: a remote call inside a transaction

OrderService.javaholds a connection during HTTP
@Transactional
public void placeOrder(Order order) {
    orderRepository.save(order);        // connection bound at transaction start
    paymentClient.charge(order);        // 3 s HTTP call: connection held, database idle
    order.markPaid();
}
OrderService.javashort transactions around the call
public void placeOrder(Order order) {
    Long id = tx.execute(s -> orderRepository.save(order.pending()).getId());
    PaymentResult result = paymentClient.charge(order);   // no connection held
    tx.executeWithoutResult(s -> orderRepository.markPaid(id, result));
}

Splitting the transaction changes the consistency model. The order can now exist in a pending state if the payment call fails, so it needs a status and a retry or reconciliation path. That trade-off is exactly why this should be a reviewed design change, not a pool setting.

Spring Boot: open session in view

spring.jpa.open-in-view defaults to true, and Spring Boot logs a warning about it at startup. It keeps the persistence context open for the whole web request, so lazy loading during serialization or view rendering can extend how long a request depends on a connection. If your hold times are long and the thread dumps point at the web layer, set it to false and fetch what the view needs inside the service.

Fixes, matched to the cause

CauseFixWhat not to do
Connection leakFind every acquisition path and guarantee release with try-with-resources or framework templates. Close returned streams and cursors.Raise the pool size. A leak drains any size, just more slowly.
Slow queryFix the query: plan, index, batching, pagination. Consider a statement timeout so one query cannot hold a connection forever.Raise connectionTimeout. Users wait longer for the same failure.
Remote call or slow work inside a transactionMove I/O outside the transaction, keep transactions short, and add timeouts to the remote client.Add connections to match the slowest dependency.
Pool deadlockRemove nested acquisition, or size the pool with Tn × (Cm − 1) + 1. Bound worker concurrency below pool capacity.Assume it is load. It will happen at moderate concurrency too.
Creation failureFix the chained cause: max_connections budget across all instances, network, credentials, TLS.Raise maximumPoolSize. That asks the database for even more connections.
Undersized poolIncrease maximumPoolSize based on throughput × hold time plus headroom, within what the database can serve.Size by instinct. Multiply by the number of instances and check the database limit.

A bigger pool makes a leak take longer to find and a slow dependency take longer to notice.

Evidence to require before suggesting a fix

Pool timeouts get “fixed” by configuration changes that ship quickly, pass review easily, and hide the real problem until the next traffic peak. Whether the fix comes from a teammate, a Stack Overflow answer, or an AI coding assistant, ask for the evidence that matches it:

Before raising maximumPoolSize
  • Connection usage p99 is normal
  • active falls when traffic falls
  • No leak warnings without “unleaked”
  • Database has spare capacity and max_connections headroom across all instances
Before calling it a leak
  • Leak trace with no “unleaked” follow-up
  • active ratchets up with uptime
  • The acquisition path is identified in code
  • The path that skips close() is shown
Before blaming the database
  • pg_stat_activity or equivalent shows long active queries or lock waits
  • Thread dumps show holders in driver socket reads
  • The slow statement is named, with its plan

This is the same discipline we apply to any change that affects shared production resources. The diff alone is not the evidence. The evidence is how the changed code acquires, holds, and releases what it shares, and what else depends on it. For the broader practice, see How to Assess the Blast Radius of a Code Change and Pre Merge Reliability Analysis.

How Tomosu helps

Tomosu analyzes the standing codebase and each change for this kind of production reliability risk. For connection pool exhaustion, the useful output is not another “increase your pool size” suggestion. It is the map from the previous section, with evidence attached:

These signals roll up into the Production Reliability Index, so a pull request that adds an HTTP call inside a @Transactional method is flagged before merge, not after the next peak.

Scan your repository with Tomosu →

Key takeaways

Frequently asked questions

What does “Connection is not available, request timed out” mean in HikariCP?

It means a thread called getConnection() and HikariCP could not hand it a connection within connectionTimeout (30,000 ms by default). Every connection in the pool was in use and no new one could be created in time. HikariCP throws java.sql.SQLTransientConnectionException. The error is a symptom of pool starvation, not a diagnosis of why the pool was starved.

Is a HikariCP request timeout always a connection leak?

No. The same timeout is produced by a true leak, by slow queries or long transactions that hold connections too long, by remote calls made while a transaction is open, by pool deadlock from nested connection acquisition, by failures creating new connections, and by a pool that is too small for the workload. Each has different evidence and a different fix.

How do I enable HikariCP connection leak detection?

Set leakDetectionThreshold in milliseconds, for example config.setLeakDetectionThreshold(20000) or spring.datasource.hikari.leak-detection-threshold=20000 in Spring Boot. It is disabled by default (0) and the minimum accepted value is 2000 ms. When a connection is held longer than the threshold, HikariCP logs a warning with the stack trace of the code that borrowed it.

Why does HikariCP report a leak and later say the connection was unleaked?

Leak detection only measures how long a connection has been out of the pool. If the connection comes back after the warning, HikariCP logs that the previously reported leaked connection was returned to the pool. That pattern points to a long hold, such as a slow query or a remote call inside a transaction, rather than a true leak.

Should I increase maximumPoolSize to fix the timeout?

Only when the evidence shows the pool is undersized: connection hold times are normal, active returns to low levels when load drops, there are no leak reports, and the database has spare capacity. If connections are leaked or held too long, a larger pool delays the timeout and moves more load onto the database without fixing the cause.

Can @Transactional cause HikariCP pool exhaustion?

Yes. A connection is typically held for the full duration of a transaction. If a @Transactional method makes an HTTP call, waits on a queue, or does slow processing, the connection is held for that time too. Propagation.REQUIRES_NEW inside an existing transaction needs a second connection per thread, which can deadlock a pool under concurrency.

What leakDetectionThreshold value should I use?

Set it above the longest legitimate time your application holds a connection, based on measured connection usage time rather than a guess. A value that is too low floods the logs with long holds that are not leaks. A value that is too high delays the evidence you need during an incident.


A pool timeout is the last step of a longer story: a code path that acquired a connection and did not give it back in time. Tomosu maps those paths before they reach production. Assess your repository →