{"record":{"id":"de084198c2122197","repo":"apache/incubator-seata","slug":"never-begin-on-a-reloaded-globaltransaction","errorCode":null,"errorMessage":"Never BEGIN on a RELOADED GlobalTransaction. ","messagePattern":"Never BEGIN on a RELOADED GlobalTransaction\\. ","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"compatible/src/main/java/io/seata/tm/api/GlobalTransactionContext.java","lineNumber":77,"sourceCode":"        GlobalTransaction tx = getCurrent();\n        if (tx == null) {\n            return createNew();\n        }\n        return tx;\n    }\n\n    /**\n     * Reload GlobalTransaction instance according to the given XID\n     *\n     * @param xid the xid\n     * @return reloaded transaction instance.\n     * @throws TransactionException the transaction exception\n     */\n    public static GlobalTransaction reload(String xid) throws TransactionException {\n        return new DefaultGlobalTransaction(xid, GlobalStatus.UnKnown, GlobalTransactionRole.Launcher) {\n            @Override\n            public void begin(int timeout, String name) throws TransactionException {\n                throw new IllegalStateException(\"Never BEGIN on a RELOADED GlobalTransaction. \");\n            }\n        };\n    }\n}\n","sourceCodeStart":59,"sourceCodeEnd":82,"githubUrl":"https://github.com/apache/incubator-seata/blob/e01f97c6db397165050caa6764020410c2c8199a/compatible/src/main/java/io/seata/tm/api/GlobalTransactionContext.java#L59-L82","documentation":"GlobalTransactionContext.reload(xid) returns an anonymous DefaultGlobalTransaction whose begin() is hard-wired to throw IllegalStateException, because a reloaded transaction represents a global transaction that has already begun on another JVM/role — it exists only to drive commit(), rollback(), getStatusReport() etc. on an existing XID. Calling begin() on it is a programming error, not a transient failure.","triggerScenarios":"Code that fetches a transaction handle via GlobalTransactionContext.reload(xid) (e.g. after receiving an XID in an MQ message or HTTP header) and then calls begin(timeout, name) on it before doing branch work, instead of using getCurrent()/.createNew() or just registering as a participant.","commonSituations":"Developers porting code from RootContext/TransactionalTemplate patterns and assuming every GlobalTransaction can begin; saga/messaging flows where the XID is propagated and the receiver mistakenly tries to (re)open it; copy-paste of the launcher template into a participant service.","solutions":["Remove the begin() call on the reloaded instance — participants should propagate the XID (RootContext.bind(xid)) and register branches, not begin.","If you genuinely need to start a new global transaction, use GlobalTransactionContext.getCurrentOrCreate().begin(...) or the @GlobalTransactional annotation instead of reload().","Use reload(xid) only for commit/rollback/status operations on an existing transaction (e.g. a console/admin job forcing rollback by XID)."],"exampleFix":"// before\n GlobalTransaction tx = GlobalTransactionContext.reload(xid);\n tx.begin(60000, \"order-flow\"); // IllegalStateException\n\n// after (participant side: propagate XID, do branch work)\n RootContext.bind(xid);\n // ... business SQL, branch registers with TC ...\n RootContext.unbind();\n\n// after (launcher side: start a new global transaction)\n GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();\n tx.begin(60000, \"order-flow\");","handlingStrategy":"type-guard","validationCode":"// A reloaded transaction may never begin — check role/origin before calling lifecycle methods\nGlobalTransaction tx = GlobalTransactionContext.reload(xid);\n// reloaded instances are Launcher-role handles over an existing XID:\n// only commit()/rollback()/getStatus()/globalReport() are valid","typeGuard":"// Guard: only a newly created/current transaction may begin()\nboolean canBegin = (tx.getClass() != GlobalTransactionContext.reload(xid).getClass());\n// simpler: track origin yourself\nboolean isReloaded = xidProvidedExternally; // XID came from header/MQ\nif (!isReloaded) {\n    tx.begin(timeout, name);\n}","tryCatchPattern":"try {\n    tx.begin(timeout, name);\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"RELOADED\")) {\n        throw new IllegalStateException(\"XID \" + xid + \" already began elsewhere; \"\n            + \"bind it via RootContext.bind(xid) instead of begin()\", e);\n    }\n    throw e;\n}","preventionTips":["Treat reload(xid) as an admin/participant handle: commit/rollback/status only.","Participants propagate XIDs with RootContext.bind(xid); launchers use @GlobalTransactional or getCurrentOrCreate().begin().","Code-review for '.begin(' wherever GlobalTransactionContext.reload appears."],"tags":["tm","global-transaction","api-misuse","xid"],"backgroundTag":null,"analyzedSha":"e01f97c6db397165050caa6764020410c2c8199a","analyzedAt":"2026-08-14T10:23:53.097Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}