skip to content

Persistence Context & Session

The engine room of Hibernate: entity states, the first-level cache, dirty checking, and flushing — where the ORM decides what SQL to run and when. Interviewers drill here because candidates who understand the persistence context can predict Hibernate; everyone else just reacts to it.

part ofHibernateoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

Which JPA entity lifecycle callback annotations exist, and at what point in an entity's lifecycle does each of them fire?

level: juniorimportance: must knowfreq 55%

answer

  1. 7 callbacks: persist/update/remove pre+post, plus @PostLoad
  2. pre-callbacks fire at statement time, i.e. usually at flush
  3. @PreUpdate only when dirty checking says UPDATE
  4. entity method: void, no args; listener method: void, one entity arg
  5. @PostLoad also after refresh and proxy initialisation

basics

~20 s

Seven: @PrePersist and @PostPersist around the INSERT, @PreUpdate and @PostUpdate around the UPDATE, @PreRemove and @PostRemove around the DELETE, and @PostLoad after an entity is loaded from the database. Methods return void and take no arguments when declared on the entity.

solid answer

~60 s

JPA defines seven callbacks: - **@PrePersist** — when `persist()` is called on a new entity, before the INSERT. - **@PostPersist** — after the INSERT statement is executed (which is usually at flush, not at the `persist()` call). - **@PreUpdate** — at flush, when dirty checking has decided this entity needs an UPDATE, just before it is issued. - **@PostUpdate** — after that UPDATE executes. - **@PreRemove** — when `remove()` is called, before the DELETE. - **@PostRemove** — after the DELETE executes. - **@PostLoad** — after an entity's state has been loaded into the persistence context, including after a `refresh()`. On the entity itself the method must be `void` with no parameters; on a separate listener class it takes one parameter, the entity. The classic uses are stamping `createdAt` in `@PrePersist` and `updatedAt` in `@PreUpdate`, deriving a denormalised field, and decrypting or normalising a value in `@PostLoad`. The subtlety worth stating out loud: pre-callbacks fire when the *statement* is about to be issued, so their timing follows the flush, not your call site.

code

java · 24 lines
java
@Entity
public class Article {
    @Id @GeneratedValue Long id;
    String title;
    Instant createdAt;
    Instant updatedAt;
    @Transient String slug;

    @PrePersist
    void onCreate() {
        createdAt = Instant.now();
        updatedAt = createdAt;
    }

    @PreUpdate
    void onUpdate() {
        updatedAt = Instant.now();
    }

    @PostLoad
    void deriveSlug() {
        slug = title.toLowerCase().replace(' ', '-');
    }
}

go deeper

for a junior

Name all seven, pair them pre/post, and give the everyday example of createdAt in @PrePersist and updatedAt in @PreUpdate.

for a middle

Explain that pre-callbacks fire at statement time so they follow the flush, that @PreUpdate depends on dirty checking, and state the method signature rules.

for a senior

Add the identifier-generator dependency of @PostPersist timing, inheritance ordering, and the categories of writes that never fire callbacks at all.

for a principal

Frame the callbacks as a partial write-path interception: portable and convenient, but blind to bulk DML, previous values, and anything that reaches the database outside this persistence unit.

## What a callback is A lifecycle callback is a method the persistence provider invokes on your entity at a defined moment as it moves between the database and memory. You mark it with an annotation; the provider calls it. It is the standard, portable extension point for logic that must run whenever a row is written or read, without every caller remembering to run it. ## The seven annotations **@PrePersist** runs when `EntityManager.persist()` is invoked (and for entities reached by a cascade from a persisted parent), *before* the INSERT. The entity is becoming managed. This is where you set a creation timestamp, generate a UUID business key, or initialise a status. Whether the primary key is populated at this point depends on the generation strategy: with a sequence or table generator Hibernate typically has the identifier already, with an identity column the value only exists after the INSERT has run. **@PostPersist** runs after the INSERT statement has been executed. This is a common source of confusion: for most generation strategies the INSERT happens at flush time, so `@PostPersist` fires much later than the `persist()` call — and importantly still *before* commit. With an identity column the INSERT is issued immediately at `persist()`, so the two callbacks fire back to back. **@PreUpdate** runs at flush time, after Hibernate has compared the entity against its loaded snapshot and concluded that an UPDATE is required, immediately before that statement. Two consequences follow: it does not fire when nothing changed, and modifications you make to the entity inside `@PreUpdate` are still included in the UPDATE being built — which is exactly why `updatedAt` stamping works there. **@PostUpdate** runs after the UPDATE has executed. Changes made to the entity here are not in that statement; they would be picked up only by a later flush, if any. **@PreRemove** runs when `remove()` is called (or a cascade reaches the entity), before the DELETE. It is the place to clean up something derived, or to reject the deletion by throwing. **@PostRemove** runs after the DELETE statement. **@PostLoad** runs after the provider has populated an entity's state from the database — after a query, after a `find()` that hits the database, after `refresh()`, and after a lazy proxy is initialised. It is the hook for deriving transient fields, decrypting a stored value, or capturing an 'original' snapshot for later comparison. ## Method rules Declared on the entity (or a mapped superclass): `void` return type, no parameters, any visibility, and it must not be static or final. Declared on a listener class: `void`, exactly one parameter which is the entity (or `Object`). An entity may not define two methods for the same callback type. Checked exceptions are not permitted; an unchecked exception thrown from a callback propagates and marks the transaction for rollback. ## Where the timing bites Because pre-callbacks are tied to statement issuance rather than to your API call, an application that relies on `@PostPersist` to see the generated identifier gets different behaviour depending on the identifier generator. And because `@PreUpdate` is tied to dirty checking, an entity you 'changed' by setting a field to its existing value produces no callback at all — Hibernate compares against the snapshot and finds nothing to write. ## Inheritance Callbacks declared on a mapped superclass or an entity superclass are inherited, and the superclass callback runs before the subclass one for the same event. An entity can suppress inherited callbacks with `@ExcludeSuperclassListeners` (for listener classes) — for the entity's own methods, overriding the method in the subclass replaces it. ## Typical uses and their limits Stamping timestamps, maintaining a denormalised counter, validating an invariant before write, normalising a string, and deriving a transient display field on load. The limits are important and get asked as a follow-up: the callbacks fire only for entity-level operations, so bulk JPQL `update`/`delete` and native SQL bypass them entirely, and a pre-callback has no access to the previous values of the entity — it sees only the current state.

  • You call persist() and expect @PostPersist to run immediately, but it only runs later. Why?
    @PostPersist fires after the INSERT actually executes, and for sequence or table generators Hibernate defers the INSERT until flush. With an identity column the INSERT must run at persist() time to obtain the key, so the callback appears immediate. The behaviour therefore depends on the identifier generation strategy, not on the callback.
  • An entity has a @PreUpdate method that stamps updatedAt, but for one code path the column never changes. What is the likely cause?
    Nothing about the entity was actually dirty, so Hibernate issued no UPDATE and never fired the callback — for example the setter wrote the same value back, or the change was made on a detached instance that was never merged. The other common cause is that the change was made with a bulk JPQL update or native SQL, which bypasses lifecycle callbacks entirely.

saying these in an interview costs you the question

  • Believing @PostPersist always runs synchronously inside the persist() call
  • Expecting @PreUpdate to fire on every flush regardless of whether the entity is dirty
  • Assuming these callbacks fire for bulk JPQL update/delete statements or native SQL
  • Giving an entity-level callback a parameter, or declaring two methods for the same callback type on one entity
  • Thinking @PostLoad runs only for find() and not for query results, refresh, or proxy initialisation

context

open as a page

Inside a transaction you load an entity with EntityManager.find, call a setter on it, and never call persist, merge, or any save method — yet an UPDATE reaches the database when the transaction commits. Explain the mechanism that makes that happen.

level: juniorimportance: must knowfreq 76%

basics

~20 s

A loaded entity is managed by the persistence context, which kept a snapshot of the values it was loaded with. At flush (commit, by default) Hibernate compares the object with that snapshot, sees the changed field, and generates the UPDATE automatically. This is dirty checking; no save call is involved.

open as a page

In JPA/Hibernate, an entity instance is always in one of four lifecycle states with respect to a persistence context. Name those states and describe how an object moves from one to another.

level: juniorimportance: must knowfreq 80%

basics

~20 s

Transient (new, unknown to the context), managed (tracked by an open EntityManager, changes written automatically), detached (was managed, context ended), removed (scheduled for DELETE). persist, find/query, detach/clear/close, merge and remove move an object between them.

open as a page

Inside a single Hibernate Session you load the same database row twice by primary key. What do you get back the second time, and what mechanism guarantees that result?

level: juniorimportance: must knowfreq 70%

basics

~20 s

You get the very same Java instance — the two references are ==, and the second load usually issues no SQL. The persistence context is an identity map keyed by entity type plus primary key, holding at most one instance per row for the life of the session.

open as a page

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%

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.

open as a page

In JPA, what is the difference between EntityManager.persist() and EntityManager.merge(), and what happens to the object instance you hand to each of them?

level: juniorimportance: must knowfreq 82%

basics

~20 s

persist() makes a new object managed: that same instance is tracked and gets the generated id. merge() copies state from a detached instance onto a managed copy and returns it; the object you passed stays unmanaged.

open as a page

How does Hibernate's Session relate to JPA's EntityManager, and how would you reach Hibernate-only functionality when your code holds an EntityManager?

level: juniorimportance: must knowfreq 58%

basics

~10 s

EntityManager is the standard JPA interface; Session is Hibernate's native implementation of it and a superset. Since Hibernate 5.2, Session extends EntityManager, so you reach the extras with entityManager.unwrap(Session.class).

open as a page

A service loads an order object inside one transaction, hands it to a caller, and minutes later takes that same object back and passes it to EntityManager.merge in a new transaction. Explain how this can silently wipe out edits another user made in between, and what makes the database reject the stale write instead.

level: middleimportance: must knowfreq 65%

basics

~20 s

merge copies the whole detached object onto the row, so columns you never touched are rewritten with the values they had at load time, overwriting concurrent edits. A version column carried with the object makes the UPDATE version-checked, so a stale copy fails instead of clobbering.

open as a page

How does Hibernate determine which loaded objects changed, why does the UPDATE statement not appear in the SQL log at the moment you call the setter, and what does this detection cost when one transaction has loaded tens of thousands of rows?

level: middleimportance: must knowfreq 55%

basics

~20 s

Hibernate keeps a copy of each entity's values from load time and compares them property by property at flush. Statements are not sent when you call a setter: they go into an action queue and are executed at flush, which allows batching. Cost is proportional to loaded entities times mapped properties, in CPU and in snapshot memory.

open as a page

Does a JPQL or HQL query read from the session's first-level cache? Explain what Hibernate does when a query returns a row whose entity instance is already present in the persistence context with different in-memory values.

level: middleimportance: must knowfreq 55%

basics

~20 s

No — a JPQL query always goes to the database. But when hydrating results, Hibernate checks each row's id against the persistence context and returns the already-managed instance, discarding the freshly read column values. So query results can show your in-memory state, not the database's.

open as a page

JPA defines FlushModeType.AUTO and FlushModeType.COMMIT, and Hibernate's native API adds ALWAYS and MANUAL. What does each of those four flush modes do, and why would you move off the default?

level: middleimportance: must knowfreq 54%

basics

~20 s

AUTO (the default) flushes at commit and before queries that pending changes could affect. COMMIT flushes only at commit, so queries may read stale data. Hibernate's ALWAYS flushes before every query; MANUAL flushes only when you call flush() explicitly.

open as a page

Describe the Open Session in View pattern in a web application: what does it do mechanically to the persistence context's lifetime, and how does that change what happens while the response is being rendered?

level: middleimportance: must knowfreq 46%

basics

~20 s

A servlet filter or interceptor opens one Hibernate Session at the start of the request, binds it to the request thread, and closes it only after the response is rendered. Entities therefore stay managed past the service transaction, so lazy loading still works during rendering.

open as a page

A developer calls merge() on a detached JPA entity, keeps mutating the variable they passed in, and the later changes never reach the database. Explain what merge actually did and how the persistence context's one-instance-per-identity rule forces that behaviour.

level: middleimportance: must knowfreq 64%

basics

~20 s

merge copied the detached object's state onto a managed instance with the same id and returned that instance. The persistence context allows only one managed object per identity, so it cannot adopt your object. Later edits to the original are invisible.

open as a page

What roles do Hibernate's SessionFactory and JPA's EntityManagerFactory play compared with a Session or EntityManager, and which of these objects are safe to share across threads?

level: middleimportance: must knowfreq 54%

basics

~20 s

The factory is a heavyweight, immutable, thread-safe, application-scoped object holding mappings, the connection pool, caches and query plans. A Session/EntityManager is cheap, short-lived, single-threaded and owns one persistence context. Never share a session between threads.

open as a page

What are you not allowed to do inside a @PreUpdate or @PostPersist callback method on a JPA entity, and which kinds of data changes will never trigger those callbacks at all?

level: seniorimportance: must knowfreq 45%

basics

~20 s

You must not call EntityManager or Query operations, or touch other entity instances — the callback runs mid-flush. And callbacks never fire for bulk JPQL update/delete, native SQL, or changes that dirty checking does not detect on that entity's own columns.

open as a page

A unit of work removes a row and then persists a new entity carrying the same unique key value, and the flush fails with a unique-constraint violation even though the delete should have freed the key. Why does Hibernate order the statements that way, and how do you fix it?

level: seniorimportance: must knowfreq 40%

basics

~20 s

At flush time Hibernate executes actions in a fixed order — inserts first, then updates, then collection actions, with deletes last — not in the order you wrote them. The INSERT therefore runs while the old row still exists. Flush explicitly after the remove, or avoid the collision.

open as a page

A web service with Open Session in View enabled degrades badly under load, with requests queueing on the connection pool. Explain what that pattern does to connection usage and query count during response rendering, and how you would confirm it in production.

level: seniorimportance: must knowfreq 40%

basics

~20 s

Rendering triggers ad-hoc lazy loads, so the pool is being used at the end of the request, when slow serialization stretches the window. Query count grows with the rendered graph. Confirm with per-request query counts and connection-acquisition timings.

open as a page

How do you move @PrePersist and @PreUpdate logic out of the entity class into a reusable component, and in what order does JPA invoke several such components together with the entity's own callback methods?

level: middleimportance: should knowfreq 38%

basics

~20 s

Put the callback methods on a separate class and attach it with @EntityListeners on the entity or mapped superclass; listener methods take the entity as their single parameter. Order: default listeners, then superclass listeners, then this entity's listeners in declaration order, then the entity's own methods.

open as a page

What concretely goes wrong when a JPA entity class is used directly as the request and response body of an HTTP API, and what does introducing a separate DTO type at that boundary actually buy you?

level: middleimportance: should knowfreq 50%

basics

~20 s

Inbound, deserialization can set fields the caller should never control (id, version, audit, ownership) and absent fields become nulls that merge writes over real data. Outbound, the object is detached and may drag lazy graphs or leak columns. A DTO makes the writable and readable field lists explicit.

open as a page

By default the UPDATE Hibernate generates for a changed entity sets every mapped column, not just the ones that were modified. Why is it built that way, and in what situations does switching that entity to Hibernate's @DynamicUpdate pay for itself?

level: middleimportance: should knowfreq 38%

basics

~20 s

Hibernate precompiles one UPDATE per entity so the statement can be cached and batched — hence all columns. @DynamicUpdate builds the SQL per flush from the changed columns only, which helps with wide tables, large LOB or JSON columns, column-level triggers, and versionless optimistic locking, at the cost of per-flush SQL generation and weaker statement reuse.

open as a page

Describe what EntityManager.detach(entity), EntityManager.clear() and closing the EntityManager each do to the lifecycle state of the entities involved, and what happens to changes made to those entities that had not been flushed yet.

level: middleimportance: should knowfreq 45%

basics

~20 s

All three move managed instances to the detached state: detach() one instance, clear() every instance, close() ends the context entirely. Unflushed changes on those instances are silently discarded — no dirty check can run on an entity the context no longer tracks.

open as a page

You call EntityManager.remove(entity) on a managed entity inside a transaction. What state is that instance in afterwards, when does the DELETE statement actually reach the database, and what happens if you call persist() on the same instance before the transaction commits?

level: middleimportance: should knowfreq 50%

basics

~20 s

It becomes removed: still in the persistence context, deletion queued. The DELETE runs at the next flush — an explicit flush, a query that forces one, or commit. Calling persist() on it before then cancels the removal and makes it managed again.

open as a page

Under Hibernate's default flush mode, a JPQL query sometimes triggers a flush of pending changes and sometimes does not. What decides that, and why do native SQL queries behave differently?

level: middleimportance: should knowfreq 42%

basics

~20 s

Hibernate compares the tables a JPQL query reads with the tables the pending actions would modify, and flushes only when they overlap. It cannot parse native SQL, so it conservatively flushes everything unless you declare the query's synchronized entities or query spaces.

open as a page

What is the difference between JPA's EntityManager.find() and EntityManager.getReference() when loading an entity by primary key, and when would you deliberately choose getReference()?

level: middleimportance: should knowfreq 52%

basics

~20 s

find() hits the database (or cache) and returns the loaded entity or null. getReference() returns a lazy proxy with only the id set, with no query until a non-id property is touched, and throws EntityNotFoundException at that point if the row is missing.

open as a page

Hibernate's native Session historically offered save(), update() and saveOrUpdate() alongside JPA's persist() and merge(). How do those legacy operations differ in behaviour, and what is their status in modern Hibernate?

level: middleimportance: should knowfreq 44%

basics

~20 s

save() inserts and returns the generated id, update() re-attaches a detached instance and forces an UPDATE, saveOrUpdate() picks between them by inspecting the id or version. All three are deprecated in Hibernate 6 and gone in Hibernate 7; use persist and merge.

open as a page

When do standard JPA lifecycle callbacks stop being enough, so that you reach for Hibernate's Interceptor or its event listener SPI instead? What can those do that an @PreUpdate method cannot?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Reach for them when you need the previous values of an entity, cross-cutting behaviour without annotating entities, collection-change events, or logic that must run after commit. Hibernate's Interceptor receives both current and previous state arrays; the event SPI adds post-commit and collection events.

open as a page

An entity mapped with @OneToMany(orphanRemoval = true) is detached, its child collection is replaced with a brand-new ArrayList rebuilt from an incoming payload, and the entity is merged back. What can go wrong, and why does Hibernate care which collection instance the entity holds?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Hibernate replaces your collection with its own tracking wrapper. Assigning a new list to a managed entity with orphanRemoval throws "a collection with cascade all-delete-orphan was no longer referenced". And on merge, any child missing from the incoming list is treated as an orphan and deleted — so a truncated payload silently deletes rows.

open as a page

You call EntityManager.merge with an entity whose @Version field is older than the value now stored in the row. Walk through what Hibernate does internally: where the two versions are compared, at which point the failure surfaces, and what happens instead if that version field is null or the row was deleted meanwhile.

level: seniorimportance: should knowfreq 38%

basics

~20 s

merge fetches the current row, compares your carried version with it, and fails immediately with an optimistic-lock error if yours is older. If versions match, the flush still issues a version-qualified UPDATE that fails on a zero-row result. A null version makes the object look new, so Hibernate tries an INSERT; a deleted row is re-created rather than reported.

open as a page

Your logs show Hibernate issuing an UPDATE for the same entity in transactions where the application never modifies it. What kinds of mapping or type problems cause these repeated no-op updates, and how do you pin down which column is responsible?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Some property compares unequal to its loaded snapshot even though nothing meaningful changed — typically a type whose write-then-read round trip is not value-preserving: BigDecimal scale, date/time precision or class mismatch, a custom or JSON type without correct equality and deep copy, trimmed CHAR padding, or a mutable value replaced on read. Temporarily enabling dynamic updates names the column in the SQL.

open as a page

When Hibernate flushes a persistence context, which entity lifecycle states can produce SQL statements and which are ignored entirely? Include what happens when a managed entity holds a reference to an object that was never persisted.

level: seniorimportance: should knowfreq 40%

basics

~20 s

Flush walks only the entities the context tracks. Managed ones can yield INSERT or UPDATE, removed ones yield DELETE. Transient and detached objects are not tracked, so they produce nothing — except a transient object reachable from a managed entity, which raises a TransientObjectException unless PERSIST cascades.

open as a page

showing 1–30 of 40