{"record":{"id":"4a788059cef3e85f","repo":"brettwooldridge/HikariCP","slug":"connection-is-closed","errorCode":null,"errorMessage":"Connection is closed","messagePattern":"Connection is closed","errorType":"exception","errorClass":"SQLException","httpStatus":null,"severity":"error","filePath":"src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java","lineNumber":559,"sourceCode":"\n      private static Connection getClosedConnection()\n      {\n         InvocationHandler handler = (proxy, method, args) -> {\n            final String methodName = method.getName();\n            switch (methodName) {\n               case \"isClosed\":\n                  return Boolean.TRUE;\n               case \"isValid\":\n                  return Boolean.FALSE;\n               case \"abort\":\n                  return Void.TYPE;\n               case \"close\":\n                  return Void.TYPE;\n               case \"toString\":\n                  return ClosedConnection.class.getCanonicalName();\n            }\n\n            throw new SQLException(\"Connection is closed\");\n         };\n\n         return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[] { Connection.class }, handler);\n      }\n   }\n}\n","sourceCodeStart":541,"sourceCodeEnd":566,"githubUrl":"https://github.com/brettwooldridge/HikariCP/blob/a4d93f4f85517f90e632b795486d7102e933d7ff/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java#L541-L566","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Use try-with-resources so connections cannot be used after close","Do not cache connections across requests/threads; borrow per unit of work","Ensure async work completes before the owning request returns the connection","Check isClosed() defensively in code paths that may outlive the borrow scope"],"exampleFix":"// before\nConnection c = ds.getConnection();\nuse(c); c.close();\nuseLater(c); // SQLException: Connection is closed\n\n// after\ntry (Connection c = ds.getConnection()) {\n   use(c);\n   useLater(c); // inside scope\n}","handlingStrategy":"try-catch","validationCode":"if (!conn.isClosed()) { conn.prepareStatement(...); }","typeGuard":null,"tryCatchPattern":"try { stmt = conn.prepareStatement(sql); }\ncatch (SQLException e) {\n    if (\"Connection is closed\".equals(e.getMessage())) { /* re-borrow a connection and retry the unit of work */ }\n    else throw e;\n}","preventionTips":["Always use try-with-resources for connections/statements","Never share a connection across threads or requests","Complete async work before the owner returns the connection"],"tags":["hikaricp","connection","use-after-close","lifecycle"],"backgroundTag":null,"analyzedSha":"a4d93f4f85517f90e632b795486d7102e933d7ff","analyzedAt":"2026-08-14T12:11:37.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}