alibaba/druid · error · SQLException
statement is closed
Error message
statement is closed
What it means
Thrown by DruidPooledStatement.checkOpen() when the statement is closed AND the owning DruidPooledConnection has a non-null disableError. The chained disableError is the root cause that disabled the underlying connection (socket error, fatal SQLException, etc.), so this variant tells you the close was a consequence of connection invalidation rather than a normal close.
Source
Thrown at core/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java:183
}
public DruidPooledConnection getPoolableConnection() {
return conn;
}
public Statement getStatement() {
return stmt;
}
protected void checkOpen() throws SQLException {
if (closed) {
Throwable disableError = null;
if (this.conn != null) {
disableError = this.conn.getDisableError();
}
if (disableError != null) {
throw new SQLException("statement is closed", disableError);
} else {
throw new SQLException("statement is closed");
}
}
}
protected void clearResultSet() {
if (resultSetTrace == null) {
return;
}
for (ResultSet rs : resultSetTrace) {
try {
if (!rs.isClosed()) {
rs.close();
}
} catch (SQLException ex) {
LOG.error("clearResultSet error", ex);View on GitHub (pinned to fa8dc99126)
Solutions
- Stop using the Statement and Connection immediately; the disableError cause identifies the real failure.
- Obtain a fresh connection and re-prepare the statement; do not attempt to recover the closed one.
- Wrap DB calls in try-with-resources for Connection, Statement, and ResultSet so handles are always released in scope.
- Inspect the chained Throwable (getCause()) to fix the underlying DB/network issue.
Example fix
// before
Statement st = conn.createStatement();
try { st.execute(bigQuery); } catch (SQLException ignore) {}
st.executeQuery(nextSql); // throws 'statement is closed' with disableError
// after
try (Connection c = ds.getConnection();
Statement st = c.createStatement()) {
st.execute(bigQuery);
} Defensive patterns
Strategy: try-catch
Validate before calling
if (stmt.isClosed()) {
// the chained cause on the underlying conn identifies the failure;
// discard statement and connection
throw new IllegalStateException("statement already closed");
} Type guard
public static boolean usable(Statement s) {
try { return s != null && !s.isClosed(); } catch (SQLException e) { return false; }
} Try / catch
try {
stmt.executeQuery(sql);
} catch (SQLException e) {
if ("statement is closed".equals(e.getMessage()) && e.getCause() != null) {
log.warn("connection disabled by: {}", e.getCause());
}
throw e;
} Prevention
- Scope every Statement in try-with-resources.
- When a query throws, treat both Statement and Connection as poisoned and discard them.
- Inspect the chained disableError to root-cause the underlying DB failure.
When it happens
Trigger: Calling any statement method after closed==true, where the connection was disabled due to an underlying error: socket timeout, fatal DB error propagated to the pool, validation failure, or explicit pool.disableConnection(). checkOpen() is invoked by virtually every delegated Statement method.
Common situations: Reusing a Statement after the connection died mid-query; catching an exception from executeQuery but continuing to use the same Statement object; statement caching returning a stale handle after a DB restart.
Related errors
- not support
- validationQuery didn't return a row
- maxWaitThreadCount {}, current wait Thread count {}
- onFatalError, activeCount {}, onFatalErrorMaxActive {}
- wait millis {waitMillis}, active {activeCount}, maxActive {m
AI-assisted analysis of alibaba/druid@fa8dc99126 (2026-08-14).
Data as JSON: /api/errors/e37f27b44fc0476c.
Report an issue: GitHub.