← Debugging Guides

阅读中文版本

HikariCP — Connection is not available, request timed out after 30000ms

Diagnose HikariCP connection pool exhaustion, read active and pending counts, find connection-holding code (missing close, long transactions), and size the pool correctly.

intermediatedebuggingjavaconnection-poolhikari

Symptoms

  • java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms
  • Logs show thousands of threads in WAITING on HikariPool.getConnection while a few threads hold connections
  • The app recovers instantly when load stops but degrades continuously under sustained traffic

Possible causes

  • A code path obtains a connection and leaks it (missing try-with-resources or early returns before close)
  • Long transactions hold connections while doing slow work: HTTP calls, file IO, or sleeps inside the transaction
  • Pool size is smaller than real concurrency, and request handling waits on connection acquisition
  • A prepared-statement or result-set leak keeps the underlying connection checked out

Diagnosis steps

Read the pool metrics

curl -s localhost:8080/actuator/metrics/hikaricp.connections.active; curl -s localhost:8080/actuator/metrics/hikaricp.connections.pending

Interpretation

Active pinned at maximum plus pending growing by the second is exhaustion; active staying below max while timing out points to leak detection or connection-level errors.

Find the holders

jstack <pid> | grep -B5 "HikariPool.getConnection" | head -30; or enable leak detection: spring.datasource.hikari.leak-detection-threshold: 60000

Interpretation

Leak detection logs a stack trace for connections held over the threshold — the exact code path that never returns them.

Audit transaction boundaries

grep -Rn "@Transactional" src/ | head -20

Interpretation

Transactional methods doing remote calls hold pool connections for the whole call duration; each slow downstream shrinks the pool.

Root cause

The pool hands out a fixed number of connections. Exhaustion means holders retain connections longer than the arrival rate of requests: a leak removes some permanently, and long transactions multiply the holding time per request. New requests then queue for the configured connectionTimeout and fail with SQLTransientConnectionException.

Fix

Close resources deterministically with try-with-resources, move slow work out of transaction boundaries, size the pool from connection-time budget rather than thread count, enable leak detection in staging, and make downstream calls resilient with timeouts shorter than the connection timeout.

Risk note

Oversizing the pool shifts the queue to the database, exhausting its connection limit and hurting every other service. Size from measured holding times.

Verify the fix

Under peak load, active connections stay below the max, pending stays near zero, and no acquisition timeout appears in logs over a full soak.

Symptom walkthrough

The exception text contains the configured budget and the pool name:

java.sql.SQLTransientConnectionException:
HikariPool-1 - Connection is not available, request timed out
after 30000ms.

The metrics pair tells you which failure mode you have:

hikaricp.connections.active    = 10   <- pinned at max
hikaricp.connections.pending    = 214  <- growing every second

Active at maximum with pending climbing is classic saturation. The decisive tool is leak detection with a threshold — it prints the acquisition stack of every connection held longer than the threshold, converting a mystery into a file and line:

Fix in practice

Close deterministically; the pattern is non-negotiable:

try (Connection c = ds.getConnection();
     PreparedStatement ps = c.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
    return map(rs);
} // all three closed on every path

Shrink transaction scope — the pool is a connection-time budget, not a thread budget:

public Order place(OrderRequest req) {
    var quote = pricingClient.quote(req);    // HTTP call: OUTSIDE transaction
    return tx.run(() -> orderDao.insert(quote.toOrder())); // milliseconds
}

Enable leak detection outside production, and watch the wait ladder:

spring:
  datasource:
    hikari:
      leak-detection-threshold: 60000
      maximum-pool-size: 12        # from measured hold-time, not cores

And bound every downstream call to less than connectionTimeout, so a slow dependency degrades its own requests instead of the whole pool.