redis/jedis · error · IllegalStateException

EXEC without MULTI

Error message

EXEC without MULTI

What it means

exec() can only run while a transaction is open. ReliableTransaction tracks this with the inMulti flag and throws IllegalStateException('EXEC without MULTI') if exec() is called before multi() or after a previous exec()/discard() closed the transaction. This is a client-side state guard preventing a protocol desync with the server.

Solutions

  1. Call multi() before exec(), or obtain the transaction through an API that starts MULTI automatically.
  2. Create a new Transaction instance for each transaction instead of reusing a closed one.
  3. Guard exec() behind a check of the transaction status/inMulti flag in your code.

Example fix

// before
Transaction t = jedis.multi();
t.exec();
t.exec(); // IllegalStateException

// after
Transaction t = jedis.multi();
List<Object> results = t.exec();
// open a new transaction for the next batch
Defensive patterns

Strategy: type-guard

Validate before calling

if (!transaction.isOpen()) { // or track your own multiOpen flag
  throw new IllegalStateException("Call multi() before exec()");
}

Type guard

boolean canExec(Transaction t) { return t != null && t.status().isOpen(); }

Try / catch

try {
  results = transaction.exec();
} catch (IllegalStateException e) {
  // transaction already closed or never opened: create a new one
  transaction = jedis.multi();
}

Prevention

When it happens

Trigger: Calling exec() on a transaction that was never opened with multi(); calling exec() twice on the same ReliableTransaction; reusing a transaction object after discard() or after it was reset/cleared.

Common situations: Code that calls exec() in a finally block while the transaction creation path failed before multi(); loop code re-executing a finished transaction; manually constructed ReliableTransaction instances bypassing factory methods.

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/3c84b549ae4b4f88. Report an issue: GitHub.

Appendix: source

Thrown at src/main/java/redis/clients/jedis/ReliableTransaction.java:161

    }
  }

  @Deprecated // TODO: private
  public final void clear() {
    if (broken) {
      return;
    }
    if (inMulti) {
      discard();
    } else if (inWatch) {
      unwatch();
    }
  }

  @Override
  public List<Object> exec() {
    if (!inMulti) {
      throw new IllegalStateException("EXEC without MULTI");
    }

    try {
      // processPipelinedResponses(pipelinedResponses.size());
      // do nothing
      connection.sendCommand(EXEC);

      List<Object> unformatted = connection.getObjectMultiBulkReply();
      if (unformatted == null) {
        pipelinedResponses.clear();
        return null;
      }

      List<Object> formatted = new ArrayList<>(unformatted.size());
      for (Object o : unformatted) {
        try {
          Response<?> response = pipelinedResponses.poll();
          response.set(o);

View on GitHub (pinned to 6dac31d4c2)