JPA fires callback methods at well-defined points in an entity's lifecycle — @PrePersist before an INSERT, @PostLoad after a SELECT, and so on — letting you run logic like setting timestamps or validating without scattering it across services. They're annotations on methods, defined by the jakarta.persistence spec.
The seven callbacks
@PrePersist before INSERT (entity becomes managed via persist)
@PostPersist after INSERT (the row now exists; PK is assigned)
@PostLoad after the entity is loaded from the DB into the context
@PreUpdate before an UPDATE (only fires if dirty checking found changes)
@PostUpdate after the UPDATE
@PreRemove before a DELETE
@PostRemove after the DELETE
A common use is auditing timestamps:
@Entity
public class Order {
@Id @GeneratedValue Long id;
Instant createdAt;
Instant updatedAt;
@PrePersist
void onCreate() { createdAt = updatedAt = Instant.now(); }
@PreUpdate
void onUpdate() { updatedAt = Instant.now(); }
}
The method must be void, take no arguments, and live on the entity (or on a separate listener class via @EntityListeners).
Timing subtleties that trip people up
@PrePersist runs when persist() is called, which may be before the actual INSERT (Hibernate batches DML until flush) — but the entity is already managed. @PostPersist runs after the INSERT, so an @GeneratedValue identity PK is available there but not necessarily in @PrePersist. @PreUpdate only fires when dirty checking detects a real change at flush — touch a field back to its original value and it won't fire. And crucially, bulk @Modifying JPQL UPDATE/DELETE bypasses these callbacks entirely — they operate on managed entities, not on direct SQL.
Don't hand-roll auditing
Spring Data already does the common case. With @EnableJpaAuditing and AuditingEntityListener, the annotations @CreatedDate, @LastModifiedDate, @CreatedBy, @LastModifiedBy populate audit fields automatically — built on these same callbacks.
@EntityListeners(AuditingEntityListener.class)
public class Order {
@CreatedDate Instant createdAt;
@LastModifiedDate Instant updatedAt;
}