{"record":{"id":"9dba5c5ce4b6a952","repo":"redis/jedis","slug":"discard-without-multi","errorCode":null,"errorMessage":"DISCARD without MULTI","messagePattern":"DISCARD without MULTI","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/main/java/redis/clients/jedis/ReliableTransaction.java","lineNumber":199,"sourceCode":"        } catch (JedisDataException e) {\n          formatted.add(e);\n        }\n      }\n      return formatted;\n    } catch (JedisConnectionException jce) {\n      broken = true;\n      throw jce;\n    } finally {\n      inMulti = false;\n      inWatch = false;\n      pipelinedResponses.clear();\n    }\n  }\n\n  @Override\n  public String discard() {\n    if (!inMulti) {\n      throw new IllegalStateException(\"DISCARD without MULTI\");\n    }\n\n    try {\n      // processPipelinedResponses(pipelinedResponses.size());\n      // do nothing\n      connection.sendCommand(DISCARD);\n      String status = connection.getStatusCodeReply();\n      if (!\"OK\".equals(status)) {\n        throw new JedisException(\"DISCARD command failed. Received response: \" + status);\n      }\n      return status;\n    } catch (JedisConnectionException jce) {\n      broken = true;\n      throw jce;\n    } finally {\n      inMulti = false;\n      inWatch = false;\n      pipelinedResponses.clear();","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/redis/jedis/blob/6dac31d4c224fb3257c216f3985340c6f500cdcb/src/main/java/redis/clients/jedis/ReliableTransaction.java#L181-L217","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Only call discard() inside the same try block after multi() succeeded.","Track transaction state (or use status()) and skip discard() when no MULTI is open.","On failure before multi(), just abandon/close the transaction object without discarding."],"exampleFix":"// before\nTransaction t = jedis.multi(); // may fail before MULTI\ntry {\n  ...\n} finally {\n  t.discard(); // IllegalStateException if MULTI never ran\n}\n\n// after\nTransaction t = jedis.multi();\ntry {\n  ...\n  t.exec();\n} catch (Exception e) {\n  t.discard();\n  throw e;\n}","handlingStrategy":"type-guard","validationCode":"if (!transactionOpened) { // your own flag set right after multi()\n  return; // nothing to discard\n}","typeGuard":"boolean canDiscard(Transaction t) { return t != null && t.status().isOpen(); }","tryCatchPattern":"try {\n  transaction.discard();\n} catch (IllegalStateException e) {\n  // no MULTI open: ignore, transaction already ended\n}","preventionTips":["Set a boolean after multi() succeeds and only discard when it is true.","Discard only inside the try block that follows a successful multi().","Avoid unconditional discard in cleanup paths for transactions that may not have opened."],"tags":["transaction","state","discard"],"backgroundTag":"invalid-state-transition","analyzedSha":"6dac31d4c224fb3257c216f3985340c6f500cdcb","analyzedAt":"2026-09-08T04:55:01.204Z","contentChangedAt":"2026-09-08T04:55:01.204Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}