tursodatabase/turso · error · SQLException

Cannot change isolation level while transaction is active.

Error message

Cannot change isolation level while transaction is active.

What it means

TursoConnection.setTransactionIsolation() rejects the call while inTransaction is true. Turso starts transactions lazily — once autocommit is off, executing any non-control statement begins a transaction implicitly — so the connection can be inside one even without an explicit BEGIN.

Source

Thrown at bindings/java/src/main/java/tech/turso/core/TursoConnection.java:294

    }
  }

  /**
   * Sets the transaction isolation level.
   *
   * @param level one of the following {@code Connection} constants: {@code
   *     Connection.TRANSACTION_READ_UNCOMMITTED}, {@code Connection.TRANSACTION_READ_COMMITTED},
   *     {@code Connection.TRANSACTION_REPEATABLE_READ}, or {@code
   *     Connection.TRANSACTION_SERIALIZABLE}.
   * @throws SQLException if a database access error occurs, this method is called on a closed
   *     connection or the given parameter is not one of the {@code Connection} constants
   */
  public void setTransactionIsolation(int level) throws SQLException {
    synchronized (transactionLock) {
      checkOpen();

      if (inTransaction) {
        throw new SQLException("Cannot change isolation level while transaction is active.");
      }

      if (level != Connection.TRANSACTION_READ_UNCOMMITTED
          && level != Connection.TRANSACTION_READ_COMMITTED
          && level != Connection.TRANSACTION_REPEATABLE_READ
          && level != Connection.TRANSACTION_SERIALIZABLE) {
        throw new SQLException("Invalid transaction isolation level: " + level);
      }

      this.transactionIsolation = level;
    }
  }

  /**
   * Retrieves the current transaction isolation level.
   *
   * @return the current transaction isolation level
   * @throws SQLException if a database access error occurs or the connection is closed

View on GitHub (pinned to bad083fafb)

Solutions

  1. Commit or roll back the active transaction first: if (!conn.getAutoCommit()) { conn.commit(); }
  2. Set the isolation level once immediately after obtaining the connection, before executing any statement
  3. In pool interceptors, only touch isolation after verifying the connection is idle (no lazy transaction open)

Example fix

// before
conn.setAutoCommit(false);
try (Statement st = conn.createStatement()) { st.execute("SELECT 1"); }
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); // throws

// after
if (!conn.getAutoCommit()) conn.commit();   // end the lazy transaction
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
Defensive patterns

Strategy: validation

Validate before calling

if (!conn.getAutoCommit()) {
  conn.commit();   // end any lazily-started transaction first
}
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

Try / catch

try {
  conn.setTransactionIsolation(level);
} catch (SQLException e) {
  if (e.getMessage() != null && e.getMessage().contains("transaction is active")) {
    conn.commit();
    conn.setTransactionIsolation(level); // retry on a quiescent connection
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Disabling autocommit, executing any statement (implicit BEGIN via the lazy transaction starter), then calling setTransactionIsolation(...) before commit()/rollback() completes the transaction.

Common situations: Connection-pool code that resets isolation on borrow/return while a user's transaction is still active; Spring's DataSourceUtils adjusting isolation per transaction definition; long-lived connections reused across test methods without cleanup.

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/4f880e0a5e354494. Report an issue: GitHub.