hibernate/hibernate-orm · error · HibernateException

Flush during cascade is dangerous

Error message

Flush during cascade is dangerous

What it means

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().

Solutions

  1. Move the flush out of the listener/interceptor: collect work in the listener, perform it after the save cascade completes
  2. Do not run queries with auto-flush inside callbacks; defer to after-commit hooks or @Transactional boundaries
  3. If early SQL is required, restructure so the callback only mutates state and the container flushes normally at commit
  4. Audit via Envers/hibernate-envers or transaction hooks instead of querying inside callbacks

Example fix

// before
@PrePersist
void onPrePersist() {
    auditRepo.log(this); // internally queries -> flush during cascade -> HibernateException
}

// after
@PrePersist
void onPrePersist() {
    AuditQueue.enqueue(this); // just record, no DB access
}
// flush/insert happens after the cascade, e.g. in an entity listener registered via
// EventListenerGroup or after transaction completion
Defensive patterns

Strategy: try-catch

Validate before calling

// Inside listeners/interceptors, defer DB work instead of flushing
if (session.getPersistenceContext().getCascadeLevel() > 0) {
    AuditQueue.enqueue(this); // defer: no flush/query during cascade
} else {
    auditRepo.log(this);
}

Try / catch

try {
    session.flush();
} catch (HibernateException e) {
    if (e.getMessage().contains("Flush during cascade")) {
        // we are inside a cascade callback: defer the flush to commit time
        deferredFlushRequired = true;
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/2730f4e91e2c3c88. Report an issue: GitHub.

Appendix: source

Thrown at hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java:1460

					.fireEventOnEachListener( dirtyCheckEvent,
							DirtyCheckEventListener::onDirtyCheck );
			return dirtyCheckEvent.isDirty();
		}
	}

	@Override
	public void flush() {
		checkOpen();
		fireFlush();
	}

	private void fireFlush() {
		if ( !isReadOnly() ) {
			try {
				pulseTransactionCoordinator();
				checkTransactionNeededForUpdateOperation();
				if ( persistenceContext.getCascadeLevel() > 0 ) {
					throw new HibernateException( "Flush during cascade is dangerous" );
				}
				eventListenerGroups.eventListenerGroup_FLUSH
						.fireEventOnEachListener( new FlushEvent( this ),
								FlushEventListener::onFlush );
				delayedAfterCompletion();
			}
			catch (RuntimeException e) {
				throw getExceptionConverter().convert( e );
			}
		}
	}

	/**
	 * Used for auto flushing shared/child session as part of the parent session's auto flush.
	 */
	@Override
	public void propagateFlush() {
		if ( isClosed() ) {

View on GitHub (pinned to fad1729dce)