{"record":{"id":"85d40cdecf29b248","repo":"hibernate/hibernate-orm","slug":"setrollbackonly-called-on-inactive-transaction","errorCode":null,"errorMessage":"setRollbackOnly() called on inactive transaction (in JPA compliant mode)","messagePattern":"setRollbackOnly\\(\\) called on inactive transaction \\(in JPA compliant mode\\)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java","lineNumber":202,"sourceCode":"\t@Override\n\tpublic void markRollbackOnly() {\n\t\t// This is the Hibernate-specific API, whereas setRollbackOnly is the\n\t\t// JPA-defined API. In our opinion, it's much more user-friendly to\n\t\t// always allow the client to indicate that the transaction should\n\t\t// not be allowed to commit.\n\t\tif ( isActive() ) {\n\t\t\tinternalGetTransactionDriverControl().markRollbackOnly();\n\t\t}\n\t\t// else noop for an inactive transaction\n\t}\n\n\t@Override\n\tpublic void setRollbackOnly() {\n\t\tif ( !isActive() ) {\n\t\t\tif ( jpaCompliance ) {\n\t\t\t\t// This is the JPA-defined version of this operation,\n\t\t\t\t// so we must check that the transaction is active\n\t\t\t\tthrow new IllegalStateException( \"setRollbackOnly() called on inactive transaction (in JPA compliant mode)\" );\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// JpaCompliance disables the check, so this method\n\t\t\t\t// is equivalent to our native markRollbackOnly()\n\t\t\t\tCORE_LOGGER.setRollbackOnlyCalledOnInactiveTransaction();\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tmarkRollbackOnly();\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean getRollbackOnly() {\n\t\tif ( jpaCompliance && !isActive() ) {\n\t\t\tthrow new IllegalStateException( \"getRollbackOnly() called on inactive transaction (in JPA compliant mode)\" );\n\t\t}\n\t\telse {","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java#L184-L220","documentation":"setRollbackOnly() is the JPA-defined way to mark a transaction rollback-only, and JPA requires an active transaction. In JPA-compliance mode TransactionImpl throws IllegalStateException when the transaction is inactive; in native mode it just logs setRollbackOnlyCalledOnInactiveTransaction and continues (equivalent to the lenient markRollbackOnly()).","triggerScenarios":"tx.setRollbackOnly() called before begin() or after commit()/rollback() completed, with hibernate.jpa.compliance.transaction=true.","commonSituations":"Exception handlers marking rollback-only even on paths where no transaction ever started; shared validation/audit code running both inside and outside transactions; retry loops calling setRollbackOnly before restarting work.","solutions":["Guard the call: if (tx.isActive()) { tx.setRollbackOnly(); }","Prefer native markRollbackOnly() when lenient behavior is desired across mixed paths","Move setRollbackOnly() into the branch that actually owns the failed transaction"],"exampleFix":"// before\ncatch (BusinessException e) {\n    tx.setRollbackOnly(); // inactive tx, JPA mode -> IllegalStateException\n}\n\n// after\ncatch (BusinessException e) {\n    if (tx.isActive()) {\n        tx.setRollbackOnly();\n    }\n}","handlingStrategy":"validation","validationCode":"if (tx.isActive()) {\n    tx.setRollbackOnly();\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Guard setRollbackOnly() with isActive() in shared handlers","Prefer native markRollbackOnly() for lenient behavior outside transactions","Mark rollback-only only in the branch that owns the failed transaction"],"tags":["hibernate","transaction","rollback-only","jpa-compliance","inactive"],"backgroundTag":"rollback-inactive-transaction","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}