{"record":{"id":"b3e7d91fea4cb673","repo":"redis/jedis","slug":"exec-without-multi-b3e7d9","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/Transaction.java","lineNumber":169,"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      // ignore QUEUED (or ERROR)\n      // processPipelinedResponses(pipelinedResponses.size());\n      List<Object> queuedCmdResponses = connection.getMany(1 + pipelinedResponses.size());\n\n\n      connection.sendCommand(EXEC);\n\n      List<Object> unformatted;\n      try {\n        unformatted = connection.getObjectMultiBulkReply();\n      } catch (JedisDataException jce) {\n        // A command may fail to be queued, so there may be an error before EXEC is called\n        // In this case, the server will discard all commands in the transaction and return the EXECABORT error.\n        // Enhance the final error with suppressed errors.\n        queuedCmdResponses.stream()","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/redis/jedis/blob/6dac31d4c224fb3257c216f3985340c6f500cdcb/src/main/java/redis/clients/jedis/Transaction.java#L151-L187","documentation":"Transaction.exec() must run inside a MULTI block. If WATCH/MONITOR-style usage or a manual-MULTI transaction never called multi(), the transaction is not active and Jedis throws IllegalStateException before sending EXEC.","triggerScenarios":"Creating a Transaction with transaction(false) (manual MULTI) and calling exec() without first calling multi(); calling exec() twice — the second call after the block already ended.","commonSituations":"Using WATCH + manual MULTI flows where an early return/exception skipped multi(); reusing a Transaction object after a prior exec; conditional logic that only sometimes enters MULTI.","solutions":["Call transaction.multi() before exec() when using manual mode (doMulti=false).","Prefer transaction(true) or transaction() so MULTI is issued automatically.","Track whether the transaction was already exec'd; create a new Transaction for a new block.","Use try/finally to ensure multi() is called on the path that reaches exec()."],"exampleFix":"// before\nTransaction t = jedis.transaction(false);\nt.watch(\"k\");\nList<Object> res = t.exec(); // throws: no multi()\n// after\nTransaction t = jedis.transaction(false);\nt.watch(\"k\");\nt.multi();\nList<Object> res = t.exec();","handlingStrategy":"validation","validationCode":"if (!inMultiManually) tx.multi(); // ensure MULTI before exec","typeGuard":null,"tryCatchPattern":"try {\n  List<Object> res = tx.exec();\n} catch (IllegalStateException e) {\n  log.error(\"Transaction not in MULTI: {}\", e.getMessage());\n}","preventionTips":["Use transaction(true) to let Jedis issue MULTI automatically","Never reuse a Transaction after exec()","Ensure multi() is called in manual-MULTI flows before exec()"],"tags":["transaction","multi-exec","jedis","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"}