alibaba/druid · error · SQLException

connection closed

Error message

connection closed

What it means

Thrown by DruidPooledConnection.checkStateInternal() when the connection is closed AND a disableError is set. The disableError (the exception that caused Druid to disable the connection) is chained as cause, giving you the original failure (e.g. a fatal DB error) alongside the 'closed' status.

Source

Thrown at core/src/main/java/com/alibaba/druid/pool/DruidPooledConnection.java:1176

            asyncCloseEnabled = false;
        }

        if (asyncCloseEnabled) {
            lock.lock();
            try {
                checkStateInternal();
            } finally {
                lock.unlock();
            }
        } else {
            checkStateInternal();
        }
    }

    private void checkStateInternal() throws SQLException {
        if (closed) {
            if (disableError != null) {
                throw new SQLException("connection closed", disableError);
            } else {
                throw new SQLException("connection closed");
            }
        }

        if (disable) {
            if (disableError != null) {
                throw new SQLException("connection disabled", disableError);
            } else {
                throw new SQLException("connection disabled");
            }
        }

        if (holder == null) {
            if (disableError != null) {
                throw new SQLException("connection holder is null", disableError);
            } else {
                throw new SQLException("connection holder is null");

View on GitHub (pinned to fa8dc99126)

Solutions

  1. Always use try-with-resources for Connection/Statement/ResultSet so a closed connection is never used again.
  2. Read the chained disableError to find why Druid closed it (fatal SQL error, validation failure) and fix the upstream cause.
  3. Avoid holding a Connection across method/RPC boundaries where async-close or eviction can reclaim it.
  4. If asyncCloseConnectionEnable is on and causing races, review whether you need it; ensure statements complete before returning the connection.

Example fix

// before — connection reused after a fatal error closed it
Connection c = pool.borrow();
runQuery(c); // fatal error -> c closed
runQuery(c); // SQLException("connection closed", disableError)

// after — fresh connection each unit of work
try (Connection c = pool.borrow()) {
    runQuery(c);
}
Defensive patterns

Strategy: validation

Validate before calling

if (pooledConn.isClosed()) {
    Throwable why = pooledConn.getDisableError();
    throw new IllegalStateException("connection already closed" + (why == null ? "" : ": " + why));
}
// proceed to use the connection

Type guard

boolean usable = pooledConn != null && !pooledConn.isClosed()
    && !pooledConn.isDisable() && pooledConn.getHolder() != null;

Try / catch

try {
    pooledConn.createStatement();
} catch (SQLException e) {
    if ("connection closed".equals(e.getMessage()) && e.getCause() != null) {
        // disableError explains why Druid closed it; borrow fresh
        log.error("conn closed by Druid due to: {}", e.getCause());
        borrowFreshAndRetry();
    } else throw e;
}

Prevention

When it happens

Trigger: Any JDBC operation on a DruidPooledConnection whose closed flag is true and disableError is non-null triggers checkState() -> checkStateInternal(), throwing at line 1176. Typically the connection was closed by Druid's async-close / keep-alive / fatal-error handling after a detected failure, and the application still holds a stale reference.

Common situations: Using a connection after handleFatalError disabled and closed it (exceptionSorter flagged a fatal error); keepAlive/eviction closed an idle connection the app still references; async-close (asyncCloseConnectionEnable) racing with a slow statement; a connection returned to a caller after the pool reclaimed it; leaked connection reused across requests.

Understand the failure class

Related errors


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