brettwooldridge/HikariCP · error · SQLException

Connection is closed

Error message

Connection is closed

What it means

When a HikariCP proxy connection has been closed (or its entry evicted), the delegate is replaced with a synthetic ClosedConnection dynamic proxy. Any method call other than close()/isClosed()/isValid()/abort()/toString() on it throws SQLException 'Connection is closed'. This makes use-after-close fail fast instead of touching a recycled real connection.

Source

Thrown at src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java:559

      private static Connection getClosedConnection()
      {
         InvocationHandler handler = (proxy, method, args) -> {
            final String methodName = method.getName();
            switch (methodName) {
               case "isClosed":
                  return Boolean.TRUE;
               case "isValid":
                  return Boolean.FALSE;
               case "abort":
                  return Void.TYPE;
               case "close":
                  return Void.TYPE;
               case "toString":
                  return ClosedConnection.class.getCanonicalName();
            }

            throw new SQLException("Connection is closed");
         };

         return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[] { Connection.class }, handler);
      }
   }
}

View on GitHub (pinned to a4d93f4f85)

Solutions

  1. Use try-with-resources so connections cannot be used after close
  2. Do not cache connections across requests/threads; borrow per unit of work
  3. Ensure async work completes before the owning request returns the connection
  4. Check isClosed() defensively in code paths that may outlive the borrow scope

Example fix

// before
Connection c = ds.getConnection();
use(c); c.close();
useLater(c); // SQLException: Connection is closed

// after
try (Connection c = ds.getConnection()) {
   use(c);
   useLater(c); // inside scope
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!conn.isClosed()) { conn.prepareStatement(...); }

Try / catch

try { stmt = conn.prepareStatement(sql); }
catch (SQLException e) {
    if ("Connection is closed".equals(e.getMessage())) { /* re-borrow a connection and retry the unit of work */ }
    else throw e;
}

Prevention

When it happens

Trigger: Using a Connection after close() (explicit or try-with-resources exit); holding a connection past connectionTimeout/maxLifetime and continuing after HikariCP evicts it; async code capturing the connection and running after the request returned it to the pool.

Common situations: Forgotten close ordering, connection leaked then recycled to another thread, background jobs using a request-scoped connection, frameworks returning connections early (e.g. after transaction commit in some setups).

Related errors


AI-assisted analysis of brettwooldridge/HikariCP@a4d93f4f85 (2026-08-14). Data as JSON: /api/errors/4a788059cef3e85f. Report an issue: GitHub.