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

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)

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.