{"record":{"id":"2730f4e91e2c3c88","repo":"hibernate/hibernate-orm","slug":"flush-during-cascade-is-dangerous","errorCode":null,"errorMessage":"Flush during cascade is dangerous","messagePattern":"Flush during cascade is dangerous","errorType":"exception","errorClass":"HibernateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java","lineNumber":1460,"sourceCode":"\t\t\t\t\t.fireEventOnEachListener( dirtyCheckEvent,\n\t\t\t\t\t\t\tDirtyCheckEventListener::onDirtyCheck );\n\t\t\treturn dirtyCheckEvent.isDirty();\n\t\t}\n\t}\n\n\t@Override\n\tpublic void flush() {\n\t\tcheckOpen();\n\t\tfireFlush();\n\t}\n\n\tprivate void fireFlush() {\n\t\tif ( !isReadOnly() ) {\n\t\t\ttry {\n\t\t\t\tpulseTransactionCoordinator();\n\t\t\t\tcheckTransactionNeededForUpdateOperation();\n\t\t\t\tif ( persistenceContext.getCascadeLevel() > 0 ) {\n\t\t\t\t\tthrow new HibernateException( \"Flush during cascade is dangerous\" );\n\t\t\t\t}\n\t\t\t\teventListenerGroups.eventListenerGroup_FLUSH\n\t\t\t\t\t\t.fireEventOnEachListener( new FlushEvent( this ),\n\t\t\t\t\t\t\t\tFlushEventListener::onFlush );\n\t\t\t\tdelayedAfterCompletion();\n\t\t\t}\n\t\t\tcatch (RuntimeException e) {\n\t\t\t\tthrow getExceptionConverter().convert( e );\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Used for auto flushing shared/child session as part of the parent session's auto flush.\n\t */\n\t@Override\n\tpublic void propagateFlush() {\n\t\tif ( isClosed() ) {","sourceCodeStart":1442,"sourceCodeEnd":1478,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java#L1442-L1478","documentation":"SessionImpl.fireFlush() refuses to flush while persistenceContext.getCascadeLevel() > 0 — i.e. while Hibernate is in the middle of cascading save/delete through an object graph. Flushing mid-cascade can reorder SQL and re-save or lose entities, so HibernateException('Flush during cascade is dangerous') aborts it. The check applies to explicit flush() and internal transactional flushes that route through fireFlush().","triggerScenarios":"Calling session.flush() (directly or via query with FlushMode.AUTO triggering a managed flush) from inside an entity listener (@PrePersist/@PreUpdate), an Interceptor, a cascade callback, or an association action that runs while the cascade level is elevated. Typical: flush in onPersist listener, or executing a query inside onSave of a custom interceptor.","commonSituations":"Business logic inside JPA entity listeners that calls repository save (which flushes); interceptors doing audit queries at onSave time; flush-mode AUTO queries executed from within event listeners; recursive saves where application code flushes inside cascade-driven hooks.","solutions":["Move the flush out of the listener/interceptor: collect work in the listener, perform it after the save cascade completes","Do not run queries with auto-flush inside callbacks; defer to after-commit hooks or @Transactional boundaries","If early SQL is required, restructure so the callback only mutates state and the container flushes normally at commit","Audit via Envers/hibernate-envers or transaction hooks instead of querying inside callbacks"],"exampleFix":"// before\n@PrePersist\nvoid onPrePersist() {\n    auditRepo.log(this); // internally queries -> flush during cascade -> HibernateException\n}\n\n// after\n@PrePersist\nvoid onPrePersist() {\n    AuditQueue.enqueue(this); // just record, no DB access\n}\n// flush/insert happens after the cascade, e.g. in an entity listener registered via\n// EventListenerGroup or after transaction completion","handlingStrategy":"try-catch","validationCode":"// Inside listeners/interceptors, defer DB work instead of flushing\nif (session.getPersistenceContext().getCascadeLevel() > 0) {\n    AuditQueue.enqueue(this); // defer: no flush/query during cascade\n} else {\n    auditRepo.log(this);\n}","typeGuard":null,"tryCatchPattern":"try {\n    session.flush();\n} catch (HibernateException e) {\n    if (e.getMessage().contains(\"Flush during cascade\")) {\n        // we are inside a cascade callback: defer the flush to commit time\n        deferredFlushRequired = true;\n    } else {\n        throw e;\n    }\n}","preventionTips":["Never call repository/query APIs from @PrePersist/@PreUpdate listeners or interceptor callbacks","Collect side effects in listeners and apply them after the cascade or transaction completes","Keep entity listeners free of Session/EntityManager usage; use Envers or event listeners registered at the factory level for auditing"],"tags":["hibernate","flush","cascade","entity-listener","interceptor","lifecycle-callback"],"backgroundTag":"flush-during-cascade","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}