skip to content

Where do you place the audit fields and the AuditingEntityListener, and what configuration mistakes make auditing silently fail?

level: middleimportance: should knowfreq 45%

answer

  1. @MappedSuperclass base + @EntityListeners once, entities extend it
  2. @MappedSuperclass = inherited columns, no table of its own
  3. silent fail: no @EnableJpaAuditing / no listener / no AuditorAware
  4. @Column(updatable=false) on created cols
  5. dates fill but *By null -> missing AuditorAware

basics

~10 s

Put the four fields plus @EntityListeners(AuditingEntityListener.class) on a shared @MappedSuperclass base class every entity extends. Silent failures usually come from missing @EnableJpaAuditing, missing @EntityListeners, or no AuditorAware bean for the *By fields.

solid answer

~40 s

The idiom is a @MappedSuperclass base class (e.g. AuditableEntity) that declares @CreatedDate/@LastModifiedDate/@CreatedBy/@LastModifiedBy and carries @EntityListeners(AuditingEntityListener.class); every @Entity extends it, so the listener and columns are inherited without duplication. @MappedSuperclass means its columns map into each subclass's own table but it isn't itself an entity. The classic silent-failure list: (1) forgetting @EnableJpaAuditing entirely; (2) putting @EntityListeners on the base but the listener still not registered because you rely on it and a subclass overrides @EntityListeners; (3) no AuditorAware bean so *By fields stay null while dates work; (4) importing the wrong annotations; (5) two @EnableJpaAuditing configs or multiple AuditorAware beans without auditorAwareRef causing ambiguity. Adding @Column(updatable=false) to the created columns prevents accidental overwrite on update.

code

kotlin · 23 lines
kotlin
@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
abstract class AuditableEntity {
    @CreatedDate
    @Column(updatable = false)
    var createdDate: Instant? = null

    @LastModifiedDate
    var lastModifiedDate: Instant? = null

    @CreatedBy
    @Column(updatable = false)
    var createdBy: String? = null

    @LastModifiedBy
    var lastModifiedBy: String? = null
}

@Entity
class Article(
    @Id @GeneratedValue var id: Long? = null,
    var title: String,
) : AuditableEntity()

go deeper

for a junior

Know the fields live on a shared base class the entities extend.

for a middle

Explain @MappedSuperclass semantics, the @EntityListeners placement, and the common silent-failure causes.

for a senior

Add updatable=false rationale, orm.xml default-listener option, and a diagnostic method (dates vs *By).

for a principal

Standardize the base class across the codebase, decide global default-listener vs per-base, and account for reactive/multi-datasource variants.

## Placement: the @MappedSuperclass idiom You almost never repeat the four audit fields on each entity. Instead: ```java @MappedSuperclass @EntityListeners(AuditingEntityListener.class) public abstract class AuditableEntity { @CreatedDate @Column(updatable = false) private Instant createdDate; @LastModifiedDate private Instant lastModifiedDate; @CreatedBy @Column(updatable = false) private String createdBy; @LastModifiedBy private String lastModifiedBy; // getters/setters } ``` - **`@MappedSuperclass`** (JPA) tells the provider: this class is not an entity and has no table, but its mapped fields are inherited into each subclass and become columns in the subclass's table. Perfect for shared audit columns. - **`@EntityListeners` on the base** is inherited by subclasses, so the listener runs for every entity that extends it — you register it once. An alternative to `@EntityListeners` on every base is to register `AuditingEntityListener` as a **default entity listener** in `orm.xml` (`<persistence-unit-defaults><entity-listeners>`), applying it to all entities globally. Rarely used, but valid. ## Optionally implement the Auditable interface Spring Data offers `Auditable<U, ID, T>` and `AbstractAuditable`, but most teams prefer their own `@MappedSuperclass` with fields, avoiding the interface's boilerplate. ## The silent-failure checklist Auditing failures are almost always silent (null columns, no exception). The usual causes: 1. **No `@EnableJpaAuditing`** — the single most common. The annotations are inert without it. Also ensure the config class is actually scanned. 2. **Listener not registered** — base class lacks `@EntityListeners`, or a subclass redeclares `@EntityListeners(SomethingElse.class)` which *replaces* rather than adds (use `@EntityListeners` carefully; multiple listeners can be listed). 3. **No `AuditorAware` bean** — `@CreatedBy/@LastModifiedBy` stay null while dates populate; a strong tell that the auditor provider is missing. 4. **Wrong annotations** — using something other than `org.springframework.data.annotation.*`. 5. **Ambiguous config** — more than one `AuditorAware` bean without `@EnableJpaAuditing(auditorAwareRef = "...")`, or duplicate `@EnableJpaAuditing` declarations. 6. **`updatable=false` mistakes** — omitting it on created columns is not a failure but risks overwrite; putting it on modified columns *is* a bug (they'd never update). 7. **Entity not going through JPA** — if the row is written by bulk/native SQL, no callback fires (covered separately). ## Verifying quickly A good sanity check: save a new entity, flush, and assert all four fields are non-null within an authenticated test (use `@WithMockUser` and a fixed `DateTimeProvider`). If dates fill but *By doesn't, it's the `AuditorAware`; if nothing fills, it's `@EnableJpaAuditing`/listener. ## Reactive note For Spring Data R2DBC/MongoDB reactive, use `@EnableR2dbcAuditing`/`@EnableReactiveMongoAuditing` and `ReactiveAuditorAware`; the JPA-specific `@EnableJpaAuditing` won't apply there.

  • Dates populate but createdBy/lastModifiedBy are always null. What's the diagnosis?
    The AuditorAware bean is missing or returns Optional.empty(). Timestamps come from the DateTimeProvider (always available); the *By fields need an AuditorAware supplying the principal, so their absence points straight to it.
  • Why annotate the created columns with @Column(updatable = false)?
    So Hibernate never includes them in UPDATE statements, guaranteeing creation metadata can't be overwritten on later modifications — a defense beyond the listener only writing them on persist.

saying these in an interview costs you the question

  • Putting @Column(updatable=false) on the last-modified columns (they'd never update)
  • Duplicating the four fields on every entity instead of a @MappedSuperclass
  • Thinking @MappedSuperclass creates its own table
  • Using @EnableJpaAuditing in a reactive R2DBC/Mongo app (need the reactive enabler)

context