redis/jedis · error · IllegalStateException

WATCH inside MULTI is not allowed

Error message

WATCH inside MULTI is not allowed

What it means

MultiDbTransaction supports optimistic locking via WATCH only before a transaction block starts. Once multi() has been called (inMulti == true), Redis forbids WATCH, so this method throws IllegalStateException("WATCH inside MULTI is not allowed"). The transaction is still valid; only this call is rejected.

Solutions

  1. Call watch(...) before multi(...) on the transaction
  2. Use a fresh MultiDbTransaction instance for each MULTI block and WATCH on it first
  3. Restructure so keys are watched in a separate pre-transaction phase

Example fix

// before
Transaction t = ...;
t.multi();
t.watch("key");
// after
Transaction t = ...;
t.watch("key");
t.multi();
Defensive patterns

Strategy: validation

Validate before calling

if (transactionInMultiState(t)) throw new IllegalStateException("call watch() before multi()");

Type guard

boolean canWatch(MultiDbTransaction t) { return !t.isInMulti(); }

Try / catch

try { t.watch(keys); } catch (IllegalStateException e) { /* re-create transaction and watch before multi */ }

Prevention

When it happens

Trigger: Calling watch(String... keys) after multi() on the same MultiDbTransaction instance.

Common situations: Reused transaction objects where multi() was invoked earlier in the same flow; mixing Watch/Transaction API patterns; refactored code that moved watch() after multi().

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/59df668baeffa82f. Report an issue: GitHub.

Appendix: source

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

  public MultiDbTransaction(MultiDbConnectionProvider provider, boolean doMulti,
      CommandObjects commandObjects) {
    super(commandObjects);
    this.connectionSupplier = new MultiDbConnectionSupplier(provider);

    if (doMulti) {
      multi();
    }
  }

  @Override
  public final void multi() {
    inMulti = true;
  }

  @Override
  public final String watch(String... keys) {
    if (inMulti) {
      throw new IllegalStateException("WATCH inside MULTI is not allowed");
    }
    String status = appendCommand(commandObjects.watch(keys)).get();
    inWatch = true;
    return status;
  }

  @Override
  public final String watch(byte[]... keys) {
    if (inMulti) {
      throw new IllegalStateException("WATCH inside MULTI is not allowed");
    }
    String status = appendCommand(commandObjects.watch(keys)).get();
    inWatch = true;
    return status;
  }

  @Override
  public final String unwatch() {

View on GitHub (pinned to 6dac31d4c2)