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

setTransactionIsolation() refuses to run while a transaction is active on the connection. Transactions begin lazily: once setAutoCommit(false) is set, the first prepared non-BEGIN/COMMIT/ROLLBACK statement starts one, and from that moment changing the isolation level is disallowed until you commit or roll back.

Solutions

  1. Set the isolation level before any statement executes: right after getConnection and setAutoCommit(false), before the first prepare
  2. Clean connections before returning them to a pool: rollback any pending work (rollback() is a safe no-op when autoCommit is false and no transaction started)
  3. In pool reset logic, order operations: rollback first, then setAutoCommit, then setTransactionIsolation

Example fix

// before
conn.setAutoCommit(false);
executeWork(conn);            // lazy BEGIN fires here
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); // throws

// after
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
conn.setAutoCommit(false);
executeWork(conn);
conn.commit();
Defensive patterns

Strategy: validation

Validate before calling

// reset to a clean state before changing isolation
if (!conn.getAutoCommit()) {
  conn.rollback(); // clears any lazily-started transaction (no-op if none started)
}
conn.setTransactionIsolation(level);

Try / catch

try {
  conn.setTransactionIsolation(level);
} catch (SQLException e) {
  if (e.getMessage().contains("transaction is active")) {
    if (!conn.getAutoCommit()) conn.rollback();
    conn.setTransactionIsolation(level); // retry once on a clean connection
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling setTransactionIsolation after setAutoCommit(false) plus any statement; connection pools calling setTransactionIsolation on checkout when the previous borrower left a transaction open (no commit/rollback before return); changing isolation mid-unit-of-work.

Common situations: Pool adapters (DBCP, HikariCP-style wrappers) that reset isolation on borrow and hit connections returned dirty; application code that begins work, then configures isolation; error paths that skip the cleanup rollback.

Related errors


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

Appendix: 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 244cde92a7)