We had a callback that “audited every save” — except it silently skipped about half of them. The @PreUpdate on the AuditListener ran correctly for every web-layer save. It never ran for the nightly batch job. The batch used HQL bulk updates. Nobody remembered that bulk operations bypass the persistence context entirely, so lifecycle callbacks never fire for them. The audit log looked complete. It was missing six months of batch changes.
That is bug four in this list. Here are all five, each one a real failure mode with the code that produces it and the fix.
Technical Note: JPA vs. Hibernate Behavior
It is important to distinguish that all annotations discussed in this guide (like @PrePersist, @PostUpdate, etc.) are defined by the Jakarta Persistence API (JPA) specification. Hibernate 7 serves as the implementation provider. While the API is standard, specific behaviors such as dirty checking algorithms, the exact timing of the flush, and session state transitions are governed by Hibernate-specific logic.
The Problem: Fragmented Business Logic
In many legacy applications, developers scatter logic like password encryption, audit logging, and data normalization across various controllers and services.
Continue reading @PrePersist and Friends: Five Lifecycle Callback Bugs You’ll Ship If You’re Not Careful
