redis/jedis · error · IllegalStateException

DISCARD without MULTI

Error message

DISCARD without MULTI

What it means

discard() cancels an open MULTI transaction by sending DISCARD. If no transaction is open (inMulti is false), the method throws IllegalStateException('DISCARD without MULTI') instead of sending a command the server would reject anyway. Like exec(), this is a client-side state machine check.

Solutions

  1. Only call discard() inside the same try block after multi() succeeded.
  2. Track transaction state (or use status()) and skip discard() when no MULTI is open.
  3. On failure before multi(), just abandon/close the transaction object without discarding.

Example fix

// before
Transaction t = jedis.multi(); // may fail before MULTI
try {
  ...
} finally {
  t.discard(); // IllegalStateException if MULTI never ran
}

// after
Transaction t = jedis.multi();
try {
  ...
  t.exec();
} catch (Exception e) {
  t.discard();
  throw e;
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!transactionOpened) { // your own flag set right after multi()
  return; // nothing to discard
}

Type guard

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

Try / catch

try {
  transaction.discard();
} catch (IllegalStateException e) {
  // no MULTI open: ignore, transaction already ended
}

Prevention

When it happens

Trigger: Calling discard() before multi() was ever called; calling discard() after a previous exec() or discard() already ended the transaction; calling discard() on a reset/cleared transaction object.

Common situations: Cleanup/finally blocks that unconditionally call discard() on a transaction whose multi() never ran (e.g. an earlier exception); double-release in retry logic; shared transaction instances across threads.

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/9dba5c5ce4b6a952. Report an issue: GitHub.

Appendix: source

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

        } catch (JedisDataException e) {
          formatted.add(e);
        }
      }
      return formatted;
    } catch (JedisConnectionException jce) {
      broken = true;
      throw jce;
    } finally {
      inMulti = false;
      inWatch = false;
      pipelinedResponses.clear();
    }
  }

  @Override
  public String discard() {
    if (!inMulti) {
      throw new IllegalStateException("DISCARD without MULTI");
    }

    try {
      // processPipelinedResponses(pipelinedResponses.size());
      // do nothing
      connection.sendCommand(DISCARD);
      String status = connection.getStatusCodeReply();
      if (!"OK".equals(status)) {
        throw new JedisException("DISCARD command failed. Received response: " + status);
      }
      return status;
    } catch (JedisConnectionException jce) {
      broken = true;
      throw jce;
    } finally {
      inMulti = false;
      inWatch = false;
      pipelinedResponses.clear();

View on GitHub (pinned to 6dac31d4c2)