Careeroza
Lifecycle Callbacks and EntityListeners
JPA Callback Annotations
Auditing is built on standard JPA lifecycle callbacks. You can use them yourself, on the entity or in a separate listener class registered with @EntityListeners.
| Annotation | Runs |
|---|---|
@PrePersist / @PostPersist | before / after the INSERT is scheduled or executed |
@PreUpdate / @PostUpdate | before / after an UPDATE (only if the entity is dirty) |
@PreRemove / @PostRemove | before / after a delete |
@PostLoad | after the entity is loaded from the database |
public class ProductSlugListener {
@PrePersist
@PreUpdate
void makeSlug(Product p) {
p.setSlug(p.getName().toLowerCase().trim().replaceAll("[^a-z0-9]+", "-"));
}
}
@Entity
@EntityListeners({AuditingEntityListener.class, ProductSlugListener.class})
public class Product extends Auditable {
private String name;
private String slug; // "boat-airdopes-141" for SEO-friendly URLs
@PostLoad
void afterLoad() { /* e.g. compute a transient field */ }
}Simulating an Auditing Listener
Example
This is roughly what AuditingEntityListener does: on persist set all four fields, on update only the "last modified" pair. A fixed Clock keeps the output stable.
import java.time.*;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
Clock t0 = Clock.fixed(Instant.parse("2026-03-14T04:45:10Z"), ZoneOffset.UTC);
Product kurta = new Product("Cotton Kurta", 799);
new AuditingListener(t0, () -> "[email protected]").prePersist(kurta);
System.out.println("after persist: " + kurta);
kurta.price = 699;
Clock t1 = Clock.offset(t0, Duration.ofHours(26));
new AuditingListener(t1, () -> "[email protected]").preUpdate(kurta);
System.out.println("after update : " + kurta);
}
}
abstract class Auditable {
Instant createdAt, updatedAt;
String createdBy, updatedBy;
}
class Product extends Auditable {
final String name;
int price;
Product(String name, int price) { this.name = name; this.price = price; }
public String toString() {
return name + " Rs." + price + " | created " + createdAt + " by " + createdBy
+ " | updated " + updatedAt + " by " + updatedBy;
}
}
class AuditingListener {
private final Clock clock;
private final Supplier<String> auditor;
AuditingListener(Clock clock, Supplier<String> auditor) { this.clock = clock; this.auditor = auditor; }
void prePersist(Auditable e) {
Instant now = clock.instant();
e.createdAt = now; e.createdBy = auditor.get();
e.updatedAt = now; e.updatedBy = auditor.get();
}
void preUpdate(Auditable e) {
e.updatedAt = clock.instant();
e.updatedBy = auditor.get();
}
}Output
after persist: Cotton Kurta Rs.799 | created 2026-03-14T04:45:10Z by [email protected] | updated 2026-03-14T04:45:10Z by [email protected] after update : Cotton Kurta Rs.699 | created 2026-03-14T04:45:10Z by [email protected] | updated 2026-03-15T06:45:10Z by [email protected]
Rules for Callbacks
Note: Callbacks must not call the EntityManager or repositories (the JPA spec forbids it and Hibernate may be in the middle of a flush). Bulk JPQL updates (@Modifying) do not trigger callbacks, so set updatedAt in the query yourself: set p.stock = p.stock - :q, p.updatedAt = CURRENT_TIMESTAMP.