mybatis/mybatis-3 · critical · SQLException

PooledDataSource: Could not get a good connection to the…

Error message

PooledDataSource: Could not get a good connection to the database.

What it means

PooledDataSource.popConnection() loops trying to obtain a usable connection. When it repeatedly gets connections that fail pingConnectionDB() validation ('bad connections'), it counts them; once localBadConnectionCount exceeds poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance (default tolerance 3), it throws SQLException 'PooledDataSource: Could not get a good connection to the database.' This means the pool can hand out connections but they are all broken.

Solutions

  1. Check that the database is actually reachable and up (the pool found connections but every one failed validation)
  2. Enable connection validation: set poolPingEnabled=true, poolPingQuery to a cheap valid query (e.g., SELECT 1), and poolPingConnectionsNotUsedFor under the network/firewall idle timeout
  3. Reduce idle connections: lower poolMaximumIdleConnections so fewer stale sockets are cached, and ensure poolMaximumActiveConnections sizing fits DB limits
  4. If using a custom poolPingQuery, verify it executes on your DB; if the driver supports it, prefer JDBC isValid by leaving the default ping path
  5. Raise poolMaximumLocalBadConnectionTolerance only as a stopgap; the root cause is dead connections in the pool

Example fix

<!-- before: no validation, dead connections stay in pool -->
<dataSource type="POOLED">
  <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://db:3306/app"/>
</dataSource>

<!-- after -->
<dataSource type="POOLED">
  <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://db:3306/app"/>
  <property name="poolPingEnabled" value="true"/>
  <property name="poolPingQuery" value="select 1"/>
  <property name="poolPingConnectionsNotUsedFor" value="60000"/>
  <property name="poolMaximumIdleConnections" value="5"/>
</dataSource>
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight check that the DB is reachable before pool use:
try (Connection c = pooledDataSource.getConnection()) {
  if (!c.isValid(2)) throw new SQLException("DB unreachable");
}

Try / catch

int attempts = 0;
while (true) {
  try (Connection c = pooledDataSource.getConnection()) {
    return c.createStatement().executeQuery(q);
  } catch (SQLException e) {
    if (++attempts >= 3 || !e.getMessage().contains("Could not get a good connection")) throw e;
    // pool is full of dead connections; wait for revalidation cycle
  }
}

Prevention

When it happens

Trigger: poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance consecutive connections fail the PoolState ping query (PoolPingEnabled / poolPingQuery); database restarted or network dropped while pooled connections remained cached; wrong poolPingQuery for the DB dialect; driver incompatibility making isValid()/ping fail; DB max_allowed_packet or firewall idle-killing connections faster than pings detect.

Common situations: Database failover/restart behind a load balancer leaving dead sockets in the pool; aggressive firewall/LB idle timeouts (e.g., 5 min) killing pooled connections; poolPingEnabled=false so dead connections are never validated; custom poolPingQuery that is not valid SQL on the target database; MySQL wait_timeout killing idle connections.

Related errors


AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14). Data as JSON: /api/errors/a7fb8f876a9a5dd4. Report an issue: GitHub.

Appendix: source

Thrown at src/main/java/org/apache/ibatis/datasource/pooled/PooledDataSource.java:532

            conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
            conn.setCheckoutTimestamp(System.currentTimeMillis());
            conn.setLastUsedTimestamp(System.currentTimeMillis());
            state.activeConnections.add(conn);
            state.requestCount++;
            state.accumulatedRequestTime += System.currentTimeMillis() - t;
          } else {
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode()
                  + ") was returned from the pool, getting another connection.");
            }
            state.badConnectionCount++;
            localBadConnectionCount++;
            conn = null;
            if (localBadConnectionCount > poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance) {
              if (log.isDebugEnabled()) {
                log.debug("PooledDataSource: Could not get a good connection to the database.");
              }
              throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
            }
          }
        }
      } finally {
        lock.unlock();
      }

    }

    if (conn == null) {
      if (log.isDebugEnabled()) {
        log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
      }
      throw new SQLException(
          "PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
    }

    return conn;

View on GitHub (pinned to 008069adb1)