brettwooldridge/HikariCP · critical · SQLTransientConnectionException

DataSource returned null unexpectedly

Error message

DataSource returned null unexpectedly

What it means

PoolBase.newConnection() calls dataSource.getConnection() and throws SQLTransientConnectionException if the driver/datasource returns null instead of a Connection. Per JDBC the contract is to never return null, so a null return means a broken or misbehaving DataSource implementation.

Source

Thrown at src/main/java/com/zaxxer/hikari/pool/PoolBase.java:375

    *
    * @return a Connection
    */
   private Connection newConnection(final boolean isEmptyPool) throws Exception
   {
      final var start = currentTime();
      final var id = java.util.UUID.randomUUID();

      Connection connection = null;
      try {
         final var credentials = getCredentials();
         final var username = credentials.getUsername();
         final var password = credentials.getPassword();

         logger.debug("{} - Attempting to create/setup new connection ({})", poolName, id);

         connection = (username == null) ? dataSource.getConnection() : dataSource.getConnection(username, password);
         if (connection == null) {
            throw new SQLTransientConnectionException("DataSource returned null unexpectedly");
         }

         setupConnection(connection);

         lastConnectionFailure.set(null);
         connectionFailureTimestamp.set(0);

         logger.debug("{} - Established new connection ({})", poolName, id);
         return connection;
      }
      catch (Throwable t) {
         logger.debug("{} - Failed to create/setup connection ({}): {}", poolName, id, t.getMessage());

         connectionFailureTimestamp.compareAndSet(0, start);
         if (isEmptyPool && elapsedMillis(connectionFailureTimestamp.get()) > MINUTES.toMillis(1)) {
            logger.warn("{} - Pool is empty, failed to create/setup connection ({})", poolName, id, t);
            connectionFailureTimestamp.set(0);
         }

View on GitHub (pinned to a4d93f4f85)

Solutions

  1. Fix the custom DataSource: getConnection() must never return null — throw SQLException instead
  2. If using mocks in tests, stub getConnection() to return a real or proxy Connection
  3. Verify the underlying driver DataSource is correctly initialized (URL, properties) before wrapping
  4. Check for wrappers in the chain (Spring AbstractDataSource subclasses) that can return null

Example fix

// before
class RoutingDataSource extends AbstractDataSource {
   public Connection getConnection() { return target; } // target may be null
}

// after
class RoutingDataSource extends AbstractDataSource {
   public Connection getConnection() throws SQLException {
      if (target == null) throw new SQLException("no datasource for tenant " + tenantId);
      return target.getConnection();
   }
}
Defensive patterns

Strategy: validation

Validate before calling

Connection test = wrapperDataSource.getConnection();
if (test == null) throw new SQLException("wrapper returns null; fix it");

Try / catch

try { conn = ds.getConnection(); }
catch (SQLTransientConnectionException e) {
    if (e.getMessage().contains("returned null unexpectedly")) {
        // defect in custom DataSource: fix its getConnection, not the pool
    }
    throw e;
}

Prevention

When it happens

Trigger: A custom DataSource wrapper (e.g. tenant-routing or lazy wrappers) whose getConnection() returns null on unknown tenant or missing config; unit-test stubs/mocks returning null; some pooling wrappers that return null on exhaustion instead of throwing.

Common situations: Custom delegating DataSource with a missing branch, Mockito mocks not stubbed for getConnection(), decorating DataSources that forward to a null underlying source.

Related errors


AI-assisted analysis of brettwooldridge/HikariCP@a4d93f4f85 (2026-08-14). Data as JSON: /api/errors/be02fde0240139d9. Report an issue: GitHub.