mybatis/mybatis-3 · error · SQLException

Error accessing PooledConnection. Connection is invalid.

Error message

Error accessing PooledConnection. Connection is invalid.

What it means

PooledConnection is a dynamic proxy around a real JDBC Connection managed by PooledDataSource. When the pool invalidates a connection (it was closed back to the pool, detected as broken, or the pool was force-closed), the 'valid' flag is set false. Any subsequent method call on the proxy goes through checkConnection() and throws SQLException 'Error accessing PooledConnection. Connection is invalid.' instead of silently hitting a dead real connection.

Source

Thrown at src/main/java/org/apache/ibatis/datasource/pooled/PooledConnection.java:267

      dataSource.pushConnection(this);
      return null;
    }
    try {
      if (!Object.class.equals(method.getDeclaringClass())) {
        // issue #579 toString() should never fail
        // throw an SQLException instead of a Runtime
        checkConnection();
      }
      return method.invoke(realConnection, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }

  }

  private void checkConnection() throws SQLException {
    if (!valid) {
      throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
    }
  }

}

View on GitHub (pinned to 008069adb1)

Solutions

  1. Never cache or reuse a Connection after close() — get a fresh one from dataSource.getConnection() for each unit of work
  2. Audit code paths where the connection reference escapes (passed to helpers, stored in fields/ThreadLocal) and scope them to a try-with-resources block
  3. If forceCloseAll is being triggered (e.g., by finalize or a lifecycle hook), fix the shutdown ordering so workers finish before the pool closes
  4. Catch SQLException on connection use and re-acquire a connection if the message indicates invalidity (defensive, only for connection-survival logic)

Example fix

// before
Connection c = pooledDataSource.getConnection();
// ... later, after c.close() returned it to the pool
c.createStatement().execute(q); // SQLException: invalid

// after
try (Connection c = pooledDataSource.getConnection();
     Statement st = c.createStatement()) {
  st.execute(q);
}
Defensive patterns

Strategy: validation

Validate before calling

// MyBatis PooledConnection exposes no public isValid of its own;
// validate by checking your own usage discipline instead:
// never use a Connection reference after close().
// With the pool, you may probe cheaply:
try (Connection c = pooledDataSource.getConnection()) {
  if (c.isValid(1)) { /* proceed */ }
}

Try / catch

try {
  stmt = conn.createStatement();
} catch (SQLException e) {
  if ("Error accessing PooledConnection. Connection is invalid.".equals(e.getMessage())) {
    conn = pooledDataSource.getConnection(); // re-acquire and retry once
  } else throw e;
}

Prevention

When it happens

Trigger: Holding a Connection obtained from PooledDataSource after returning it to the pool (e.g., via close()) and then calling createStatement()/prepareStatement() on the stale reference; using a connection after PooledDataSource.forceCloseAll(); keeping connections past their pool lifetime; two parts of the code sharing one proxied Connection where one closes it.

Common situations: Custom connection management code that caches java.sql.Connection references instead of asking the pool each time; wrappers that call close() then continue using the connection; shutdown hooks or tests that force-close the pool while workers still use connections; Spring-managed code mixing pooled and raw connections.

Related errors


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