{"record":{"id":"6bc5a7b07e8bc314","repo":"redis/jedis","slug":"discard-without-multi-6bc5a7","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/mcf/MultiDbTransaction.java","lineNumber":219,"sourceCode":"          formatted.add(response.get());\n        } catch (JedisDataException e) {\n          formatted.add(e);\n        }\n      }\n      return formatted;\n\n    } finally {\n      inMulti = false;\n      inWatch = false;\n      commands.clear();\n      releaseConnection(serverInMultiMode);\n    }\n  }\n\n  @Override\n  public final String discard() {\n    if (!inMulti) {\n      throw new IllegalStateException(\"DISCARD without MULTI\");\n    }\n\n    try {\n      // MULTI itself is only issued from exec(), so the server never started a transaction.\n      // Buffered commands only exist locally, so there is nothing to roll back server-side\n      // unless we have already acquired a connection for pre-MULTI traffic (e.g. WATCH).\n      if (inWatch) {\n        acquireConnection().sendCommand(UNWATCH);\n        return connection.getStatusCodeReply();\n      }\n      return OK_STR;\n    } finally {\n      inMulti = false;\n      inWatch = false;\n      commands.clear();\n      releaseConnection(false);\n    }\n  }","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/redis/jedis/blob/6dac31d4c224fb3257c216f3985340c6f500cdcb/src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java#L201-L237","documentation":"discard() cancels a buffered transaction and requires that multi() was called (inMulti == true), matching Redis semantics. If MULTI was never entered, there is no transaction to discard and the method throws IllegalStateException(\"DISCARD without MULTI\").","triggerScenarios":"Calling discard() on a MultiDbTransaction before multi(), or after a prior discard()/exec() already cleared the state (close() also calls discard internally).","commonSituations":"Double cleanup in finally blocks (close() after explicit discard()); defensive discard() on a freshly created transaction; error-handling paths that discard unconditionally.","solutions":["Only call discard() after multi() was invoked; use close() for unconditional cleanup","Guard with an inMulti/state check or catch IllegalStateException","Ensure single ownership of the transaction lifecycle so close() isn't called after an explicit discard()"],"exampleFix":"// before\nTransaction t = client.transaction();\nt.discard(); // throws\n// after\nTransaction t = client.transaction();\nt.multi();\nt.set(\"k\", \"v\");\nt.discard();","handlingStrategy":"try-catch","validationCode":"if (t.isInMulti()) t.discard(); // otherwise just close()","typeGuard":"boolean canDiscard(MultiDbTransaction t) { return t.isInMulti(); }","tryCatchPattern":"try { t.discard(); } catch (IllegalStateException e) { /* transaction was never in MULTI; safe to ignore or close */ }","preventionTips":["Use close() (which handles both cases) instead of raw discard() in cleanup code","Guard discard() with the multi-state check","Avoid calling both discard() and close() on the same instance"],"tags":["redis","transactions","discard","illegal-state"],"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"}