{"record":{"id":"fb9ddc0fb4905129","repo":"hibernate/hibernate-orm","slug":"transaction-was-marked-for-rollback-only","errorCode":null,"errorMessage":"Transaction was marked for rollback only","messagePattern":"Transaction was marked for rollback only","errorType":"exception","errorClass":"RollbackException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java","lineNumber":306,"sourceCode":"\t\t\tcatch (RuntimeException e) {\n\t\t\t\t// commit failed\n\t\t\t\ttry {\n\t\t\t\t\tafterCompletionCallback( StatusTranslator.STATUS_FAILED_COMMIT );\n\t\t\t\t}\n\t\t\t\tcatch (RuntimeException e2) {\n\t\t\t\t\te.addSuppressed( e2 );\n\t\t\t\t}\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t\t// commit successful\n\t\t\tafterCompletionCallback( Status.STATUS_COMMITTED );\n\t\t}\n\n\t\tprivate void commitRollbackOnly() {\n\t\t\tJDBC_LOGGER.onCommitMarkedRollbackOnlyRollingBack();\n\t\t\trollback();\n\t\t\tif ( jpaCompliance.isJpaTransactionComplianceEnabled() ) {\n\t\t\t\tthrow new RollbackException( \"Transaction was marked for rollback only\" );\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void rollback() {\n\t\t\tif ( isActive() ) {\n\t\t\t\tjdbcResourceTransaction.rollback();\n\t\t\t\tafterCompletionCallback( Status.STATUS_ROLLEDBACK );\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\t@Nonnull\n\t\tpublic TransactionStatus getStatus() {\n\t\t\treturn jdbcResourceTransaction.getStatus();\n\t\t}\n\n\t\t@Override","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java#L288-L324","documentation":"jakarta.persistence.RollbackException (javax.persistence pre-Hibernate 6) from the resource-local coordinator's commitRollbackOnly(): the transaction was marked rollback-only — via setRollbackOnly() or by Hibernate after a failed flush — so commit() rolled back instead, and because JPA transaction compliance is enabled (the default in Hibernate 6) that outcome is reported with this exception. The data is rolled back; the message tells you the transaction was poisoned earlier.","triggerScenarios":"tx.setRollbackOnly() followed by tx.commit(); an exception during flush/SQL that the application caught and swallowed — Hibernate marks rollback-only — and commit() is attempted anyway; with Spring, an inner @Transactional (default REQUIRED) whose exception was caught by an outer caller marks the shared transaction rollback-only, so the outer commit throws this.","commonSituations":"Catch-and-continue error handling inside a transaction; Spring nested service calls where the caller swallows a RuntimeException from a transactional callee; JPA compliance enabled by default after upgrading to Hibernate 6 making previously silent rollbacks visible.","solutions":["Treat any persistence exception as terminal: roll back and start a new transaction instead of committing","In Spring, let the inner exception propagate, or annotate the inner work REQUIRES_NEW when its failure must not poison the outer transaction","Check tx.getRollbackOnly() before commit and roll back deliberately with a clear application error","If you intentionally called setRollbackOnly(), call rollback() — never commit()"],"exampleFix":"// before\ntry { em.flush(); } catch (PersistenceException e) { log.warn(\"continuing\", e); }\nem.getTransaction().commit(); // RollbackException: Transaction was marked for rollback only\n\n// after\ntry {\n  em.flush();\n  em.getTransaction().commit();\n} catch (PersistenceException e) {\n  if (em.getTransaction().isActive()) em.getTransaction().rollback();\n  throw e; // never commit a transaction that saw a persistence exception\n}","handlingStrategy":"validation","validationCode":"// decide deliberately before commit\nEntityTransaction tx = em.getTransaction();\nif (tx.getRollbackOnly()) {\n  tx.rollback();\n  throw new IllegalStateException(\"transaction was marked rollback-only; rolled back cleanly\");\n}\ntx.commit();","typeGuard":"static boolean isPoisoned(jakarta.persistence.EntityTransaction tx) {\n  return tx.getRollbackOnly();\n}","tryCatchPattern":"try {\n  tx.commit();\n} catch (jakarta.persistence.RollbackException e) {\n  // commit already rolled back: close/discard the EntityManager and surface the ORIGINAL\n  // exception that poisoned the transaction (look at your logs), then retry in a new transaction\n}","preventionTips":["Treat any persistence exception as terminal for its transaction — roll back, never continue","In Spring, do not catch exceptions thrown by nested @Transactional methods sharing your transaction","Check getRollbackOnly() at the transaction boundary to fail with a meaningful error"],"tags":["hibernate","transaction","rollback-only","jpa","spring"],"backgroundTag":"transaction-marked-rollback-only","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}