Events, Interceptors, and Filters

This section documents Hibernate ORM 7.4.x (User Guide, Introduction, Query Language Guide, Data Repositories Guide), Jakarta Persistence 3.2, Hibernate Search 8.4.x, and the Hibernate Validator / Hibernate Reactive references — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production.

Three older reference books were consulted as bibliography only while preparing these pages and are not the primary or main source for any page. All three predate Jakarta Persistence 3.2 and Hibernate ORM 6/7 (the javax.persistencejakarta.persistence namespace change, the ORM 6 query-engine rewrite, the Hibernate Search 6+ Elasticsearch backend), so the official documentation above wins on any discrepancy.

This section’s bibliography lists the reference material consulted while preparing these pages.

Hibernate exposes several extension points for running code around persistence operations, from the standardized JPA callbacks up to Hibernate’s own lower-level event system.

JPA lifecycle callbacks and entity listeners

@Entity
@EntityListeners(AuditListener.class)
public class Book {
    @PrePersist
    void onPrePersist() {
        this.createdAt = Instant.now();
    }

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

public class AuditListener {
    @PostLoad
    void onLoad(Object entity) {
        // external listener -- entity does not need to know about it
    }
}

The full set: @PrePersist/@PostPersist, @PreUpdate/@PostUpdate, @PreRemove/@PostRemove, and @PostLoad. A callback method can live directly on the entity (self-callback, as onPrePersist above) or on a separate class named via @EntityListeners (external listener, as AuditListener above) — external listeners keep cross-cutting concerns (auditing, validation) out of the entity class itself. Compare with Spring Data’s own @CreatedDate/@LastModifiedDate auditing (Spring Data JPA), which is itself implemented as an @EntityListeners(AuditingEntityListener.class) on top of exactly this mechanism — reach for the Spring Data annotations directly rather than hand-rolling the same thing again in a Spring Boot application.

The Hibernate Interceptor

A single, session-wide (or SessionFactory-wide) hook that sees every entity operation, not just callbacks declared per-entity — useful for a cross-cutting concern that needs to touch every entity type uniformly without annotating each one:

public class AuditInterceptor implements Interceptor {
    @Override
    public boolean onFlushDirty(Object entity, Object id, Object[] currentState,
            Object[] previousState, String[] propertyNames, Type[] types) {
        // inspect/modify currentState before the UPDATE is generated
        return true; // state was modified -- tell Hibernate to use it
    }
}

// registered at bootstrap:
Session session = sessionFactory.withOptions().interceptor(new AuditInterceptor()).openSession();

Interceptor methods (onSave, onFlushDirty, onDelete, …​) are called for every entity that passes through the session, in contrast to @EntityListeners, which is opted into per entity class.

The native event system: EventType, listeners, and Integrator

Beneath both of the above, every Hibernate operation is actually implemented as firing a typed event (EventType.PERSIST, EventType.LOAD, EventType.FLUSH_ENTITY, …​) to a chain of registered listeners — PrePersistEventListener, PreInsertEventListener, and so on for each event type. This is the lowest-level, most powerful extension point (it is literally how Hibernate implements its own default persistence behavior), and correspondingly the least commonly needed directly — reach for it only when neither JPA callbacks nor Interceptor can express what is needed (e.g. replacing, rather than just observing, part of the default insert/update algorithm for one specific event type).

An Integrator (org.hibernate.integrator.spi.Integrator, discovered via Java’s ServiceLoader) is how a library registers its own event listeners, type contributors, or other SPI hooks into Hibernate at bootstrap without the application needing to wire it manually — the mechanism third-party Hibernate extensions (Envers included, see Envers Auditing) use to attach themselves.

Dynamic data filters: @FilterDef/@Filter

A filter is a named, parameterized WHERE clause fragment that can be turned on or off per session, applied transparently to every query (and lazy-load) against the filtered entity/collection while active:

@Entity
@FilterDef(name = "activeOnly", parameters = @ParamDef(name = "isActive", type = Boolean.class))
@Filter(name = "activeOnly", condition = "active = :isActive")
public class Book {
    private boolean active;
}

// ...
session.enableFilter("activeOnly").setParameter("isActive", true);
List<Book> activeBooks = session.createQuery("FROM Book", Book.class).getResultList(); // filter applied automatically

Filters are per-session and off by default — forgetting to enable one is a silent no-op (the query just runs unfiltered), unlike a missing WHERE clause typo in HQL, which at least fails loudly if the syntax is wrong.

Soft delete with @SoftDelete

@SoftDelete (Hibernate 6.4+) marks an entity so a remove()/bulk-delete DELETE becomes, transparently, an UPDATE setting a deleted-marker column instead of removing the row, and every subsequent query against that entity automatically excludes soft-deleted rows — without hand-writing a filter or an active/deleted column check into every query:

@Entity
@SoftDelete(columnName = "deleted", strategy = SoftDeleteType.DELETED)
public class Book {
    // no explicit "deleted" field needed -- Hibernate manages the column directly
}

This differs from the @FilterDef/@Filter approach above in that soft-delete exclusion is always on (not an opt-in per-session toggle) and remove() itself changes meaning, rather than queries against an already-present boolean column being filtered.