skip to content

Lifecycle Callbacks & Interceptors

Hooking into entity lifecycle events for auditing, validation, and side effects — from JPA callbacks to Hibernate's Interceptor and event-listener SPI. Interviewers ask what you may not do inside a callback and how auditing columns get filled.

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

questions

5

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

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

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

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

You need a durable audit trail of every change to a set of entities: who changed what, with old and new values. Compare building it on JPA lifecycle callbacks, on Hibernate Envers, and on database triggers.

level: principalimportance: should knowfreq 28%

basics

~20 s

Callbacks are simple but blind to old values, bulk JPQL, native SQL and outside writers. Envers versions entities automatically with revision metadata and a query API, but still only sees ORM writes and grows tables. Triggers catch every write including out-of-band ones, but lose application context and are database-specific.

open as a page