{"record":{"id":"3c84b549ae4b4f88","repo":"redis/jedis","slug":"exec-without-multi","errorCode":null,"errorMessage":"EXEC without MULTI","messagePattern":"EXEC without MULTI","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/main/java/redis/clients/jedis/ReliableTransaction.java","lineNumber":161,"sourceCode":"    }\n  }\n\n  @Deprecated // TODO: private\n  public final void clear() {\n    if (broken) {\n      return;\n    }\n    if (inMulti) {\n      discard();\n    } else if (inWatch) {\n      unwatch();\n    }\n  }\n\n  @Override\n  public List<Object> exec() {\n    if (!inMulti) {\n      throw new IllegalStateException(\"EXEC without MULTI\");\n    }\n\n    try {\n      // processPipelinedResponses(pipelinedResponses.size());\n      // do nothing\n      connection.sendCommand(EXEC);\n\n      List<Object> unformatted = connection.getObjectMultiBulkReply();\n      if (unformatted == null) {\n        pipelinedResponses.clear();\n        return null;\n      }\n\n      List<Object> formatted = new ArrayList<>(unformatted.size());\n      for (Object o : unformatted) {\n        try {\n          Response<?> response = pipelinedResponses.poll();\n          response.set(o);","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/redis/jedis/blob/6dac31d4c224fb3257c216f3985340c6f500cdcb/src/main/java/redis/clients/jedis/ReliableTransaction.java#L143-L179","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call multi() before exec(), or obtain the transaction through an API that starts MULTI automatically.","Create a new Transaction instance for each transaction instead of reusing a closed one.","Guard exec() behind a check of the transaction status/inMulti flag in your code."],"exampleFix":"// before\nTransaction t = jedis.multi();\nt.exec();\nt.exec(); // IllegalStateException\n\n// after\nTransaction t = jedis.multi();\nList<Object> results = t.exec();\n// open a new transaction for the next batch","handlingStrategy":"type-guard","validationCode":"if (!transaction.isOpen()) { // or track your own multiOpen flag\n  throw new IllegalStateException(\"Call multi() before exec()\");\n}","typeGuard":"boolean canExec(Transaction t) { return t != null && t.status().isOpen(); }","tryCatchPattern":"try {\n  results = transaction.exec();\n} catch (IllegalStateException e) {\n  // transaction already closed or never opened: create a new one\n  transaction = jedis.multi();\n}","preventionTips":["Create a fresh Transaction per unit of work; never re-exec a closed transaction.","Structure code as multi() -> queue -> exec()/discard() with no early returns skipping multi().","Avoid calling exec() in finally blocks."],"tags":["transaction","state","exec"],"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"}