alibaba/druid · critical · SQLException

onFatalError, activeCount {}, onFatalErrorMaxActive {}

Error message

onFatalError, activeCount {}, onFatalErrorMaxActive {}

What it means

A circuit-breaker: once the pool has recorded a fatal error (onFatalError flag set, e.g. the DB rejected connections or a connection died from a fatal exception), if activeCount has climbed to onFatalErrorMaxActive the pool refuses new borrows and rethrows the last fatal error. This stops a sick database from being flooded with requests after it already signalled failure.

Source

Thrown at core/src/main/java/com/alibaba/druid/pool/DruidDataSource.java:1650

                    StringBuilder errorMsg = new StringBuilder();
                    errorMsg.append("onFatalError, activeCount ")
                            .append(activeCount)
                            .append(", onFatalErrorMaxActive ")
                            .append(onFatalErrorMaxActive);

                    if (lastFatalErrorTimeMillis > 0) {
                        errorMsg.append(", time '")
                                .append(StringUtils.formatDateTime19(
                                        lastFatalErrorTimeMillis, TimeZone.getDefault()))
                                .append("'");
                    }

                    if (lastFatalErrorSql != null) {
                        errorMsg.append(", sql \n")
                                .append(lastFatalErrorSql);
                    }

                    throw new SQLException(
                            errorMsg.toString(), lastFatalError);
                }

                connectCount++;

                if (createScheduler != null
                        && poolingCount == 0
                        && activeCount < maxActive
                        && createDirectCountUpdater.get(this) == 0
                        && creatingCountUpdater.get(this) == 0
                        && createScheduler instanceof ScheduledThreadPoolExecutor) {
                    ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) createScheduler;
                    if (executor.getQueue().size() > 0) {
                        if (maxWait > 0 && System.currentTimeMillis() - startTime >= maxWait) {
                            holder = null;
                            break;
                        }
                        createDirect = true;

View on GitHub (pinned to fa8dc99126)

Solutions

  1. Check the chained lastFatalError — it names the real root cause (e.g. 'Communications link failure'); fix that DB/network issue first.
  2. Inspect lastFatalErrorTimeMillis and lastFatalErrorSql in the message to locate when and on which query the DB broke.
  3. Tune onFatalErrorMaxActive upward if you want more headroom during partial outages, or disable the breaker by setting onFatalErrorMaxActive to 0 (then the guard at line 1628 is skipped).
  4. Add an outer retry/circuit-breaker (e.g. Resilience4j) so callers degrade gracefully while the pool is in fatal state, rather than hammering getConnection().

Example fix

// disable the fatal-error breaker so callers still get normal maxWait timeouts
ddataSource.setOnFatalErrorMaxActive(0);

// or keep it but let callers fall back
try (Connection c = dataSource.getConnection()) { ... }
catch (SQLException e) {
    if (dataSource.isOnFatalError()) {
        // serve degraded response / trip an upstream circuit breaker
    }
}
Defensive patterns

Strategy: fallback

Validate before calling

DruidDataSource dds = (DruidDataSource) dataSource;
// isOnFatalError / onFatalError flag plus activeCount vs onFatalErrorMaxActive
if (dds.isOnFatalError() && dds.getOnFatalErrorMaxActive() > 0
        && dds.getActiveCount() >= dds.getOnFatalErrorMaxActive()) {
    throw new ServiceUnavailableException("pool in fatal-error breaker state");
}
return dataSource.getConnection();

Try / catch

try {
    return dataSource.getConnection();
} catch (SQLException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("onFatalError")) {
        // delegate to an outer circuit breaker; degrade the request
        throw new ServiceUnavailableException("DB fatal breaker open", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: onFatalError is true (a prior fatal SQLException passed the exceptionSorter, e.g. ORA-... / Communications link failure) AND activeCount >= onFatalErrorMaxActive (>0). The caller in getConnection() hits the guard at line 1627-1629 and the assembled error (activeCount, onFatalErrorMaxActive, time, last SQL) is thrown.

Common situations: Database restart or outage under load; network partition between app and DB; DB hitting max_connections so Druid's create attempts fail fatally; onFatalErrorMaxActive set very low (default behaviour) so the breaker trips almost immediately; a bad migration/lock causing fatal errors across many connections.

Related errors


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