{"record":{"id":"59df668baeffa82f","repo":"redis/jedis","slug":"watch-inside-multi-is-not-allowed","errorCode":null,"errorMessage":"WATCH inside MULTI is not allowed","messagePattern":"WATCH inside MULTI is not allowed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java","lineNumber":82,"sourceCode":"  public MultiDbTransaction(MultiDbConnectionProvider provider, boolean doMulti,\n      CommandObjects commandObjects) {\n    super(commandObjects);\n    this.connectionSupplier = new MultiDbConnectionSupplier(provider);\n\n    if (doMulti) {\n      multi();\n    }\n  }\n\n  @Override\n  public final void multi() {\n    inMulti = true;\n  }\n\n  @Override\n  public final String watch(String... keys) {\n    if (inMulti) {\n      throw new IllegalStateException(\"WATCH inside MULTI is not allowed\");\n    }\n    String status = appendCommand(commandObjects.watch(keys)).get();\n    inWatch = true;\n    return status;\n  }\n\n  @Override\n  public final String watch(byte[]... keys) {\n    if (inMulti) {\n      throw new IllegalStateException(\"WATCH inside MULTI is not allowed\");\n    }\n    String status = appendCommand(commandObjects.watch(keys)).get();\n    inWatch = true;\n    return status;\n  }\n\n  @Override\n  public final String unwatch() {","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/redis/jedis/blob/6dac31d4c224fb3257c216f3985340c6f500cdcb/src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java#L64-L100","documentation":"MultiDbTransaction supports optimistic locking via WATCH only before a transaction block starts. Once multi() has been called (inMulti == true), Redis forbids WATCH, so this method throws IllegalStateException(\"WATCH inside MULTI is not allowed\"). The transaction is still valid; only this call is rejected.","triggerScenarios":"Calling watch(String... keys) after multi() on the same MultiDbTransaction instance.","commonSituations":"Reused transaction objects where multi() was invoked earlier in the same flow; mixing Watch/Transaction API patterns; refactored code that moved watch() after multi().","solutions":["Call watch(...) before multi(...) on the transaction","Use a fresh MultiDbTransaction instance for each MULTI block and WATCH on it first","Restructure so keys are watched in a separate pre-transaction phase"],"exampleFix":"// before\nTransaction t = ...;\nt.multi();\nt.watch(\"key\");\n// after\nTransaction t = ...;\nt.watch(\"key\");\nt.multi();","handlingStrategy":"validation","validationCode":"if (transactionInMultiState(t)) throw new IllegalStateException(\"call watch() before multi()\");","typeGuard":"boolean canWatch(MultiDbTransaction t) { return !t.isInMulti(); }","tryCatchPattern":"try { t.watch(keys); } catch (IllegalStateException e) { /* re-create transaction and watch before multi */ }","preventionTips":["Always WATCH before MULTI in your transaction helper","Wrap transaction setup in a single method enforcing the correct order","Avoid reusing transaction objects across lifecycle states"],"tags":["redis","transactions","watch","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"}