How Does the Flush Mechanism Work in Hibernate
The Flush mechanism in Hibernate and JPA is the internal engine process that translates pending in-memory entity state modifications (INSERT, UPDATE, DELETE) into physical SQL s...
The Flush mechanism in Hibernate and JPA is the internal engine process that translates pending in-memory entity state modifications (INSERT, UPDATE, DELETE) into physical SQL statements and dispatches them across the active JDBC connection into the database transaction buffer. Mastering the flush lifecycle is essential to prevent unintended deadlocks, unique constraint collisions, and database state corruption.
🟢 Junior Level
What is flush and How Does It Differ from commit?
Developers frequently conflate flush and commit:
flush(): Synchronizes the JVM memory state with the database. Pending SQL statements (INSERT,UPDATE,DELETE) are physically executed over the JDBC socket within the current open transaction. The database row modifications are visible to queries within this same transaction, but they are not yet permanently committed or visible to other database connections.commit(): Permanently finalizes the database transaction (SQLCOMMIT). The relational engine writes changes to the transaction log (WAL), releases row and table locks, and makes modifications visible to concurrent database transactions. Hibernate automatically triggersflush()immediately before everycommit().
FLUSH VS COMMIT IN THE TRANSACTION LIFECYCLE
[ Java Heap: EntityManager ]
user.setName("Anna");
│
│ 1. entityManager.flush()
▼
[ JDBC Socket -> Database: Active Transaction (Uncommitted) ]
SQL: UPDATE users SET name = 'Anna' WHERE id = 1;
(Changes visible ONLY to this connection; row locks held)
│
│ 2. transaction.commit()
▼
[ Database Storage Engine (WAL / Data Files) ]
SQL: COMMIT;
(Locks released; changes permanently visible to all transactions)
When Does Hibernate Trigger flush Automatically?
- Before Transaction Commit: Exiting a method marked with
@Transactionalautomatically triggers a flush of all accumulated entity changes. - Prior to Executing JPQL / Criteria / Native Queries: Under default
FlushModeType.AUTO, Hibernate flushes pending changes to tables targeted by the upcoming query so that the query returns fresh, consistent data. - Upon Explicit Invocation of
entityManager.flush(): Used by developers to force synchronization (e.g., during batch processing or to assign database-generated primary keys).
@Transactional
public void exampleFlush() {
User user = entityManager.find(User.class, 1L);
user.setName("New Name");
// At this point, the database still holds the old name.
// Explicit flush dispatches the SQL UPDATE immediately:
entityManager.flush();
// The UPDATE statement has reached the database, but the transaction is NOT yet committed!
// If an exception occurs next, the transaction will ROLLBACK,
// and "New Name" will be reverted by the database engine.
}
🟡 Middle Level
Flush Modes (FlushModeType)
In JPA and Hibernate, flush behavior is controlled via FlushModeType:
// Configure globally on the EntityManager
entityManager.setFlushMode(FlushModeType.AUTO); // Default
// Or configure per specific query
entityManager.createQuery("SELECT u FROM User u")
.setFlushMode(FlushModeType.COMMIT)
.getResultList();
| Flush Mode | Behavior | When to Use |
|---|---|---|
FlushModeType.AUTO |
(Default) Flushes pending changes before transaction commit and before queries that intersect with modified tables. | Standard application workflow. Enforces strict Read-Your-Own-Writes consistency within the active transaction. |
FlushModeType.COMMIT |
Flushes pending changes only during transaction commit. Subsequent SELECT queries do not trigger intermediate flushes. |
Performance optimization in high-throughput workflows where queries are known to be independent of prior writes. |
Smart AUTO Flush: Query Spaces Optimization
Early ORM engines blindly executed a full flush before every single SELECT query. Modern Hibernate uses Query Spaces optimization:
@Transactional
public void smartFlushDemo() {
User user = entityManager.find(User.class, 1L);
user.setName("Alex"); // Modified entity User (mapped to 'users' table)
// The upcoming query targets a completely unrelated table 'orders':
List<Order> orders = entityManager.createQuery("FROM Order", Order.class).getResultList();
// Hibernate analyzes the metamodel: 'orders' and 'users' query spaces do not overlap!
// ZERO FLUSH IS TRIGGERED before this query! Conserves CPU cycles and network I/O.
}
Exception: For native SQL queries (createNativeQuery), Hibernate cannot reliably parse custom SQL dialects to determine affected tables. Consequently, Hibernate defaults to executing a complete flush of all pending actions before running any native SQL query.
🔴 Senior Level
The ActionQueue: Strict Deterministic SQL Execution Order
Inside SessionImpl, all persistence operations accumulate in a dedicated queue called org.hibernate.engine.spi.ActionQueue.
When flush() runs, Hibernate does NOT execute statements in the order your Java code was called. It groups and executes all queued actions in a strict deterministic order:
STRICT ACTIONQUEUE EXECUTION ORDER
1. OrphanRemovalAction (Deletes orphan entities removed from collections)
│
▼
2. EntityInsertAction (All persist / save operations)
│
▼
3. EntityUpdateAction (All UPDATEs generated by Dirty Checking)
│
▼
4. CollectionRemoveAction (Cleans join tables for @ManyToMany / @OneToMany)
│
▼
5. CollectionUpdateAction (Updates elements within collections)
│
▼
6. CollectionRecreateAction (Re-creates collections)
│
▼
7. EntityDeleteAction (All remove / delete operations executed LAST!)
Why This Execution Order Exists:
This strict ordering guarantees relational referential integrity (Foreign Key Constraints). To link a child to a parent, the parent must be inserted first (INSERT parent -> INSERT child). Conversely, during deletions, foreign keys and associations must be cleared first, followed by child records, and finally the parent record.
The Unique Constraint Collision Trap
Because EntityInsertAction (Step 2) always executes before EntityDeleteAction (Step 7), replacing an entity with a UNIQUE constraint causes a catastrophic runtime failure:
// Assume table `users` enforces a UNIQUE constraint on column `username`
@Transactional
public void replaceUser() {
User oldUser = userRepository.findByUsername("john_doe");
entityManager.remove(oldUser); // Scheduled: EntityDeleteAction (Step 7)
User newUser = new User();
newUser.setUsername("john_doe"); // Reuses the exact same unique username
entityManager.persist(newUser); // Scheduled: EntityInsertAction (Step 2)
// At transaction commit, FLUSH executes:
// 1. ActionQueue executes INSERT INTO users (username) VALUES ('john_doe') FIRST!
// 2. Database explodes: ERROR: duplicate key value violates unique constraint!
// 3. The scheduled DELETE was never even reached!
}
The Senior Solution: Split the operations with an explicit intermediate flush():
entityManager.remove(oldUser);
entityManager.flush(); // Physically executes DELETE against the database NOW!
entityManager.persist(newUser); // INSERT proceeds smoothly without constraint collision!
Batching Optimization with order_inserts and order_updates
By default, operations in ActionQueue execute in the order they were submitted within their type bucket. If code alternates between entity types (User -> Order -> User -> Order), JDBC statement batching breaks because each type transition forces the driver to close the current PreparedStatement.
Enabling sorting options groups identical entity types together:
spring:
jpa:
properties:
hibernate:
jdbc.batch_size: 50
order_inserts: true # Groups INSERTs by entity type to enable JDBC batching
order_updates: true # Groups UPDATEs by entity type to enable JDBC batching
4 Tricky Questions
1. Can you catch a database constraint violation exception (PersistenceException) thrown during entityManager.flush() in a try-catch block and continue working with the same session?
Answer:
- Categorically NO. The JPA specification strictly states: when any
PersistenceExceptionoccurs duringflush(), the activePersistenceContextis considered corrupted and uncoordinated. - The active transaction is immediately marked as
Rollback-Onlyby the JDBC driver and transaction manager. - Any subsequent attempt to query or persist entities through the same
EntityManagerfails immediately with:Transaction rolled back because transaction was marked as rollback-only. - The session must be discarded. To log error metadata or audit failures into the database, you must execute the logging operation within an isolated, independent transaction (
@Transactional(propagation = Propagation.REQUIRES_NEW)).
2. Why does invoking entityManager.flush() NOT release JVM heap memory, unlike entityManager.clear()?
Answer:
flush()interacts exclusively with the database: it generates SQL and flushes the network buffer over the JDBC socket.- All entity instances in the JVM heap remain in the
Managedstate insideentitiesByKeyandentityEntrieswithinStatefulPersistenceContext. These are retained via strong Java references. - To release JVM heap memory, the developer must explicitly call
entityManager.clear()afterentityManager.flush(), which detaches all managed entities and permits garbage collection.
3. If you explicitly invoke entityManager.flush() inside a method marked with @Transactional(readOnly = true), what happens?
Answer:
- In Spring Framework,
@Transactional(readOnly = true)configures Hibernate’s session toFlushModeType.MANUAL(or disables automated dirty checking snapshots). - Upon method exit, the automatic pre-commit flush is completely skipped, saving CPU resources.
- However, if the developer explicitly calls
entityManager.flush()inside the method, Hibernate executes dirty checking and attempts to dispatch SQL statements over the JDBC connection. - If the underlying JDBC connection was set to
Connection.setReadOnly(true)by the transaction manager, the database driver will abort the query with an error such as:ERROR: cannot execute UPDATE in a read-only transaction.
4. Why does an auto-flush trigger before a JPQL query searching by entity fields, but NOT before entityManager.find() by primary key?
Answer:
- The
find(Entity.class, id)method inspects the First-Level Cache (entitiesByKey) first. - If the entity is already managed in memory (even if its fields have been mutated),
find()returns the modified in-memory reference immediately without querying the database. Because memory is already consistent, no expensiveflush()is required. - Conversely, a JPQL query always executes an SQL
SELECTagainst the database engine. To ensure the database sees uncommitted in-memory mutations, Hibernate must flush dirty entities first so the SQL query returns accurate results.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“The Flush mechanism in Hibernate synchronizes the in-memory
PersistenceContextwith the database by translating mutations into SQLINSERT,UPDATE, andDELETEstatements dispatched over the JDBC connection. Crucially,flush()does not commit the transaction — changes remain uncommitted and roll back if an error occurs. By default,FlushModeType.AUTOflushes changes before transaction commit and before JPQL queries targeting modified tables. Internally, Hibernate usesActionQueue, which executes statements in a strict deterministic order (all INSERTs run before any DELETEs). When replacing an entity that has a UNIQUE constraint, an intermediate explicitflush()is mandatory to prevent duplicate key constraint violations.”
Key Production Metrics & Health Checks
- Flush Count (
session.getStatistics().getFlushCount()): A high flush count relative to transaction count indicates unwanted intermediate flushes caused by queries interleaved between entity mutations. - Flush Duration: Reflects combined time spent on dirty checking comparisons plus JDBC socket write latency. Spikes above 50 ms indicate bloated persistence contexts.
- Rollback-Only Exception Rate: Monitors frequency of constraint violations and unrecoverable persistence failures during auto-flush.
Red Flags to Avoid
- ❌ “Calling
flush()permanently commits the transaction, preventing a rollback.” — Flush only writes SQL to the socket; the transaction is finalized strictly bycommit(). - ❌ “Hibernate executes SQL queries in the exact chronological order of Java method calls.” — The
ActionQueuedictates execution order (INSERTs execute before DELETEs). - ❌ “You can catch a database exception thrown during
flush()and keep using the EntityManager.” — The session is permanently corrupted and flagged asRollback-Only. - ❌ “Calling
flush()cleans up JVM memory.” — OnlyentityManager.clear()evicts entities from the L1 cache to free heap memory.
Related Topics
- What is Dirty Checking in Hibernate — Change detection triggering flush
- Describe the Entity Lifecycle in Hibernate — Managed state lifecycle
- What is the First-Level Cache in Hibernate — Persistence context storage
- What is the Difference Between persist() and merge() — Persistence operations in ActionQueue
- How to Implement Optimistic Locking in JPA — Version validation during flush