{"record":{"id":"e0e7d59fcb01d6a8","repo":"hibernate/hibernate-orm","slug":"getrollbackonly-called-on-inactive-transaction","errorCode":null,"errorMessage":"getRollbackOnly() called on inactive transaction (in JPA compliant mode)","messagePattern":"getRollbackOnly\\(\\) 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":218,"sourceCode":"\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 {\n\t\t\treturn getStatus() == TransactionStatus.MARKED_ROLLBACK;\n\t\t}\n\t}\n\n\tprotected boolean allowFailedCommitToPhysicallyRollback() {\n\t\treturn false;\n\t}\n}\n","sourceCodeStart":200,"sourceCodeEnd":229,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java#L200-L229","documentation":"getRollbackOnly() reads the rollback-only flag (status == MARKED_ROLLBACK). JPA defines this accessor only for active transactions, so in JPA-compliance mode (hibernate.jpa.compliance.transaction=true) calling it on an inactive one throws IllegalStateException; native mode simply evaluates getStatus() == MARKED_ROLLBACK, which is false when inactive.","triggerScenarios":"tx.getRollbackOnly() polled before begin() or after the transaction completed, in JPA-compliance mode — e.g. monitoring/decision code that checks the flag in a loop that spans the transaction boundary.","commonSituations":"Polling loops and health checks that query rollback-only state including outside the transaction; orchestration code shared between transactional and non-transactional steps; frameworks inspecting the flag during cleanup.","solutions":["Guard the read: boolean rollbackOnly = tx.isActive() && tx.getRollbackOnly();","Restrict flag polling to code that provably runs inside the begun transaction","In native flows, use getStatus() directly and treat non-active statuses as false"],"exampleFix":"// before\nif (tx.getRollbackOnly()) { // inactive tx, JPA mode -> IllegalStateException\n    abortPipeline();\n}\n\n// after\nif (tx.isActive() && tx.getRollbackOnly()) {\n    abortPipeline();\n}","handlingStrategy":"validation","validationCode":"boolean rollbackOnly = tx.isActive() && tx.getRollbackOnly();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Read getRollbackOnly() only inside code that provably runs in the transaction","Use getStatus() == MARKED_ROLLBACK directly in native flows","Keep monitoring/polling code out of transaction boundaries"],"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"}