skip to content

What are the four JPA auditing annotations in Spring Data, and what two things must you configure to make them populate automatically?

level: juniorimportance: must knowfreq 70%

answer

  1. 4 annotations: CreatedDate/LastModifiedDate/CreatedBy/LastModifiedBy
  2. @EnableJpaAuditing turns it on globally
  3. @EntityListeners(AuditingEntityListener.class) on entity
  4. dates auto; who needs AuditorAware
  5. @MappedSuperclass base class idiom

basics

~10 s

The annotations are @CreatedDate, @LastModifiedDate, @CreatedBy, and @LastModifiedBy on entity fields. To activate them you add @EnableJpaAuditing on a config class and attach @EntityListeners(AuditingEntityListener.class) to the entity.

solid answer

~30 s

Spring Data JPA fills four fields for you: @CreatedDate and @LastModifiedDate (timestamps) and @CreatedBy and @LastModifiedBy (the acting principal). Two pieces wire this up. First, @EnableJpaAuditing on any @Configuration class turns the feature on globally. Second, each audited entity needs @EntityListeners(AuditingEntityListener.class), which registers the JPA listener that sets those fields during persist and update lifecycle callbacks. Dates work out of the box; the @CreatedBy/@LastModifiedBy fields stay null unless you also provide an AuditorAware<T> bean that returns the current user. You typically put the four fields on a shared @MappedSuperclass base class so every entity inherits them without repetition.

code

java · 29 lines
java
@Configuration
@EnableJpaAuditing
class JpaAuditingConfig { }

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Auditable {

    @CreatedDate
    @Column(updatable = false)
    private Instant createdDate;

    @LastModifiedDate
    private Instant lastModifiedDate;

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String lastModifiedBy;
    // getters/setters
}

@Entity
public class Article extends Auditable {
    @Id @GeneratedValue Long id;
    String title;
}

go deeper

for a junior

Name the four annotations and the two wiring steps (@EnableJpaAuditing + @EntityListeners); know dates auto-fill.

for a middle

Add that @CreatedBy needs AuditorAware, and that the @MappedSuperclass base class is the standard placement.

for a senior

Discuss updatable=false on created columns, correct field types, and the org.springframework.data.annotation package distinction.

for a principal

Frame auditing as one option among Envers, DB triggers, and event-sourcing; know its consistency and testability trade-offs.

## What auditing metadata is "Auditing" here means automatically recording *who* touched a row and *when*, without writing that code in every service. Spring Data JPA provides four field-level annotations: - **`@CreatedDate`** — set once, when the entity is first persisted (INSERT). - **`@LastModifiedDate`** — set on the initial persist and re-set on every update. - **`@CreatedBy`** — the principal (user) who created the row, set once. - **`@LastModifiedBy`** — the principal who last modified the row, re-set on every update. All four live in package `org.springframework.data.annotation` (note: *not* the JPA package), so they are Spring Data annotations, not standard JPA. ## The two required wiring steps **1. Enable the feature.** Put `@EnableJpaAuditing` on a `@Configuration` class (often the main application class). This registers the infrastructure beans that make auditing work. Without it, the annotations are simply ignored. **2. Register the listener on the entity.** Annotate each audited entity with `@EntityListeners(AuditingEntityListener.class)`. `AuditingEntityListener` is a JPA entity listener that hooks the `@PrePersist` and `@PreUpdate` lifecycle events and writes the four fields at those moments. ## Field types - Date/time fields may be `Instant`, `LocalDateTime`, `Date`, `Long` (epoch millis), or Java 8 date types — Spring converts appropriately. - `@CreatedBy`/`@LastModifiedBy` are typed to whatever your `AuditorAware<T>` returns — commonly `String` (username) or a `Long`/`UUID` user id. ## The auditor piece Timestamps need no extra config — the framework reads the clock. But *who* the current user is, Spring cannot know. You supply an `AuditorAware<T>` bean whose `getCurrentAuditor()` returns an `Optional<T>` of the current principal (typically pulled from Spring Security's `SecurityContextHolder`). If you omit it, `@CreatedBy`/`@LastModifiedBy` stay null but timestamps still populate. ## Where to put the fields Repeating four fields on every entity is noise. The idiom is a `@MappedSuperclass` base class (e.g. `Auditable`) holding the four fields plus `@EntityListeners(AuditingEntityListener.class)`, which all entities extend. `@MappedSuperclass` means the base's columns are mapped into each subclass's table without the base being an entity itself. ## Common gotchas - Forgetting `@EnableJpaAuditing` — fields silently stay null. - Forgetting `@EntityListeners` on the entity (or on the base class) — same silent null. - Expecting `@CreatedBy` to fill without an `AuditorAware` bean. - Using `save()` on a *detached/merge* path and expecting `@CreatedDate` — created is only set on the first persist.

  • Which import package do @CreatedDate and friends come from, and why does it matter?
    org.springframework.data.annotation — they are Spring Data annotations, not jakarta.persistence. Importing the wrong same-named type (there isn't a JPA equivalent, but IDEs sometimes auto-import odd things) means nothing populates.
  • If you enable auditing but never define an AuditorAware bean, what happens?
    Timestamps (@CreatedDate/@LastModifiedDate) still populate from the clock, but @CreatedBy/@LastModifiedBy stay null because Spring has no source for the current principal.

saying these in an interview costs you the question

  • Thinking @EnableJpaAuditing alone is enough without @EntityListeners on the entity
  • Believing the annotations come from jakarta.persistence / standard JPA
  • Assuming @CreatedBy fills automatically without an AuditorAware bean
  • Confusing this with Hibernate Envers (which is full row-versioning/history, a different feature)

context