skip to content

In JPA/Hibernate, what does it mean to "flush" the persistence context, and how is flushing different from committing the transaction?

level: juniorimportance: must knowfreq 66%

answer

  1. write-behind action queue
  2. flush = SQL out, commit = permanent
  3. commit implies flush, never the reverse
  4. constraint errors surface at flush
  5. IDENTITY forces insert at persist

basics

~20 s

Flushing sends the INSERT/UPDATE/DELETE statements the persistence context has queued to the database inside the open transaction. Committing ends the transaction and makes them permanent. Commit always flushes first, but a flush alone can still be rolled back.

solid answer

~50 s

The persistence context is a write-behind buffer. When you call `persist`, modify a managed entity, or call `remove`, Hibernate normally does not talk to the database immediately — it records the intent in an internal action queue. **Flush** is the moment it turns that queue into SQL and sends it over the current JDBC connection, synchronizing memory with the database. **Commit** is a transaction operation: it ends the unit of work, makes changes durable and visible to other transactions. The relationship is one-way: commit triggers a flush first, but a flush does not commit. Flushed statements are still inside the transaction, invisible to other sessions, and a rollback undoes them completely. Flushes happen at commit, on an explicit `EntityManager.flush()`, and — under the default flush mode — automatically before a query whose results the pending changes could affect.

code

java · 10 lines
java
em.getTransaction().begin();

Order order = new Order("A-1");
em.persist(order);          // no INSERT yet (sequence generator)

order.setStatus("PAID");    // no UPDATE either

em.flush();                 // INSERT ... (status already PAID) is sent now

em.getTransaction().rollback(); // the flushed INSERT is undone

go deeper

for a junior

Recall the core sentence: flush sends SQL inside the transaction, commit ends the transaction and makes it permanent, and commit flushes first.

for a middle

Add why write-behind exists (batching, statement ordering) and name the three flush triggers: commit, explicit flush(), and auto-flush before a query.

for a senior

Connect it to debugging and locking: errors surface at flush time rather than at the offending line, and early flushes lengthen row-lock windows.

for a principal

Frame flush timing as a unit-of-work design decision — where write batches are pushed, how long locks are held, and how failures are surfaced in the request lifecycle.

## Write-behind, not write-through A JPA `EntityManager` (Hibernate `Session` underneath) is an in-memory unit of work called the **persistence context**. It holds every entity you loaded or created, keyed by type plus primary key, plus a snapshot of the loaded state. Operations you perform are recorded as *pending actions* in an internal **action queue** rather than executed at once. This is called write-behind, and it exists so Hibernate can batch statements, avoid writing rows you later change again, and order statements safely. ## What flush does Flushing walks the persistence context and turns pending state into SQL: - entities you called `persist()` on become `INSERT`s; - managed entities whose current field values differ from their load-time snapshot become `UPDATE`s; - entities you called `remove()` on become `DELETE`s; - collection changes become their own insert/update/delete statements. The statements go over the JDBC connection the session is using, inside whatever transaction is currently open. After a flush the database and the persistence context agree; the entities stay managed and keep being tracked. ## What commit does Commit is a *transaction* concept, not a persistence-context one. It tells the database the work of the transaction is final: locks are released, changes become visible to other transactions, and durability guarantees apply. Because Hibernate cannot let queued changes vanish, committing performs a flush first and then commits. That is why plenty of code never calls `flush()` and still writes data correctly. ## Why the distinction matters 1. **A flush is undoable.** If you flush, then throw, then roll back, nothing survives. Candidates who say "flush saves the data" are wrong in the way that matters. 2. **A flush can fail.** Constraint violations, `NOT NULL` breaches and optimistic-lock version mismatches surface *at flush time*, not when you edited the object. That is why a stack trace often points at a commit or at an unrelated query rather than at the line that set the bad value — the write was buffered until then. 3. **A flush takes locks.** Once an `UPDATE` is on the wire the row is write-locked until the transaction ends, so flushing early and committing late widens the lock window. 4. **Some operations force partial writes early.** With `GenerationType.IDENTITY`, Hibernate must run the `INSERT` during `persist()` to obtain the generated key; sequence-based generators avoid this and stay fully write-behind. ## When flushes occur - **At commit** — always. - **Explicitly**, via `EntityManager.flush()` / `Session.flush()`. - **Automatically before a query**, under the default `FlushModeType.AUTO`, so the query sees your own pending changes. Explicit flushes have legitimate uses: forcing a constraint or version error to surface where you can handle it, making pending rows visible to a native SQL statement you are about to run, and the batching idiom `flush()` + `clear()` that pushes accumulated statements out and detaches the entities so the context does not grow without bound. Flushing everywhere "to be safe" is an anti-pattern: it defeats batching, lengthens lock hold times, and hides the real design of the unit of work.

  • After calling flush(), can another transaction see the inserted row?
    No, not under normal isolation. The statements are inside your still-open transaction, so other transactions see the pre-transaction state; a reader trying to modify the same row may block on your locks. Visibility only comes with commit.
  • Give two legitimate reasons to call flush() explicitly.
    First, to make pending changes visible to a native SQL statement or stored procedure you are about to execute, since Hibernate cannot always tell that the SQL touches the same tables. Second, to force a constraint or optimistic-lock failure to surface at a controlled point, or as part of the flush-then-clear batching idiom that pushes statements out and keeps the persistence context small.

Flush is pressing Save in an editor that still holds an exclusive lock on the file; commit is closing the file so everyone else can finally see and open it. A crash before closing can still discard the saved work.

saying these in an interview costs you the question

  • Saying flush() commits or 'saves permanently' — a rollback still discards it
  • Believing every setter immediately issues an UPDATE
  • Calling flush() after every operation 'to be safe', destroying batching and holding row locks longer
  • Thinking commit does not flush, so you must call flush() before every commit
  • Assuming persist() never issues SQL — IDENTITY key generation forces the INSERT immediately

context