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

  1. Stop using the Statement and Connection immediately; the disableError cause identifies the real failure.
  2. Obtain a fresh connection and re-prepare the statement; do not attempt to recover the closed one.
  3. Wrap DB calls in try-with-resources for Connection, Statement, and ResultSet so handles are always released in scope.
  4. 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

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


AI-assisted analysis of alibaba/druid@fa8dc99126 (2026-08-14). Data as JSON: /api/errors/e37f27b44fc0476c. Report an issue: GitHub.