redis/jedis · error · JedisException

Active database has changed since transaction started

Error message

Active database has changed since transaction started

What it means

MultiDbTransaction buffers commands locally. Pre-MULTI commands (e.g. WATCH) execute immediately against the database that was active when the transaction started (initialDatabase). appendCommand throws JedisException("Active database has changed since transaction started") when a follow-up command arrives while the active database differs from initialDatabase, because commands would hit a different Redis instance and break transaction semantics.

Solutions

  1. Complete (exec/discard) the transaction before triggering or waiting for failover
  2. Catch JedisException and rebuild the transaction on the new active database (retry the whole WATCH/MULTI/EXEC cycle)
  3. Check the active database (connectionSupplier.isActiveDatabase) before starting a transaction and keep transactions short

Example fix

// before
Transaction t = client.transaction();
t.watch("k");
failover(); // active db changed
t.set("k", "v"); // throws
// after
Transaction t = client.transaction();
try {
  t.watch("k");
  t.set("k", "v");
  t.exec();
} catch (JedisException e) {
  t.close();
  t = client.transaction(); // retry on new active db
}
Defensive patterns

Strategy: retry

Validate before calling

if (!connectionSupplier.isActiveDatabase(initialDatabase)) { /* rebuild transaction on new active db */ }

Try / catch

try { resp = t.set(key, value); } catch (JedisException e) { t.close(); t = client.transaction(); /* retry full WATCH/MULTI/EXEC */ }

Prevention

When it happens

Trigger: Calling any command (via status()/response() buffering path) on a MultiDbTransaction after a failover switched the active database, while the transaction had recorded an initialDatabase and is not yet in buffered MULTI mode.

Common situations: MultiDb failover triggered mid-transaction by health check failure; long-lived transaction objects spanning a database switch; tests or apps that call failoverToAnotherDatabase while a transaction is open.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of redis/jedis@6dac31d4c2 (2026-09-08). Data as JSON: /api/errors/46dc3f27239f5de6. Report an issue: GitHub.

Appendix: source

Thrown at src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java:117

  @Override
  public final String unwatch() {
    Response<String> response = appendCommand(
      new CommandObject<>(new CommandArguments(UNWATCH), BuilderFactory.STRING));
    inWatch = false;
    // when inside MULTI, the command has only been buffered; its reply will be delivered by exec()
    return inMulti ? null : response.get();
  }

  @Override
  protected final <T> Response<T> appendCommand(CommandObject<T> commandObject) {
    if (inMulti) {
      CommandArguments args = commandObject.getArguments();
      Response<T> response = new Response<>(commandObject.getBuilder());
      commands.add(KeyValue.of(args, response));
      return response;
    }
    if (initialDatabase != null && !connectionSupplier.isActiveDatabase(initialDatabase)) {
      throw new JedisException("Active database has changed since transaction started");
    }
    try {
      return Response.of(acquireConnection().executeCommand(commandObject));
    } catch (JedisDataException e) {
      return Response.error(e);
    }
  }

  @Override
  public void close() {
    try {
      if (inMulti) {
        discard();
      } else if (inWatch) {
        unwatch();
      }
    } finally {
      releaseConnection(false);

View on GitHub (pinned to 6dac31d4c2)