Home/Learn/Hibernate & JPA/@Auditing with Spring Data

@Auditing with Spring Data

Intermediate
Advanced

Enable @EnableJpaAuditing and annotate fields with @CreatedDate, @LastModifiedDate, @CreatedBy, @LastModifiedBy; an AuditorAware bean supplies the current user identity.

Overview

Spring Data JPA Auditing automatically populates timestamp and user fields on entity creation and update, eliminating repetitive boilerplate in every service method. By enabling @EnableJpaAuditing and extending a shared @MappedSuperclass with @CreatedDate, @LastModifiedDate, @CreatedBy, and @LastModifiedBy fields, every inheriting entity automatically records when and by whom it was created and last modified. The AuditorAware<T> bean bridges Spring Security's SecurityContextHolder to Spring Data, extracting the authenticated username. For full audit history (not just the last-modified user) use Hibernate Envers which tracks every version of every entity in dedicated audit tables.

Auditing base entity and configuration

A shared @MappedSuperclass with audit annotations is inherited by all entities. @EntityListeners(AuditingEntityListener.class) activates the listener that populates the fields.

Java — AuditableEntity MappedSuperclass
// Enable auditing in any @Configuration class
@Configuration
@EnableJpaAuditing(auditorAwareRef = "currentUserAuditor")
public class JpaConfig {}

// Shared audit base entity
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class AuditableEntity {

    @CreatedDate
    @Column(updatable = false, nullable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column(nullable = false)
    private LocalDateTime updatedAt;

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

    @LastModifiedBy
    @Column(length = 100)
    private String updatedBy;
}

// Domain entity inherits audit fields
@Entity
@Table(name = "orders")
public class Order extends AuditableEntity {
    @Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    private String status;
    private BigDecimal amount;
    // createdAt, updatedAt, createdBy, updatedBy inherited
}

AuditorAware — supplying the current user

AuditorAware<T> is called by Spring Data before persist/update to get the value for @CreatedBy and @LastModifiedBy fields.

Java — AuditorAware with Spring Security + async fallback
@Component("currentUserAuditor")
public class SecurityAuditorAware implements AuditorAware<String> {

    @Override
    public Optional<String> getCurrentAuditor() {
        return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
            .filter(Authentication::isAuthenticated)
            .filter(auth -> !"anonymousUser".equals(auth.getPrincipal()))
            .map(Authentication::getName);  // returns username/email
    }
}

// For async operations (no SecurityContext in spawned threads)
// Propagate SecurityContext manually or use a system user fallback
@Component("currentUserAuditor")
public class FallbackAuditorAware implements AuditorAware<String> {
    @Override
    public Optional<String> getCurrentAuditor() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null || !auth.isAuthenticated()) {
            return Optional.of("system");  // batch jobs, scheduled tasks
        }
        return Optional.of(auth.getName());
    }
}

Full audit history with Hibernate Envers

Spring Data Auditing only captures the latest state. Hibernate Envers records every version of an entity in audit tables, enabling temporal queries (what did this order look like at 3pm?).

Java — Hibernate Envers full audit history
<!-- pom.xml -->
<dependency>
  <groupId>org.hibernate.orm</groupId>
  <artifactId>hibernate-envers</artifactId>
</dependency>

// Mark entity for full audit history
@Entity
@Audited           // all fields tracked; use @NotAudited to exclude fields
public class Order extends AuditableEntity {
    private String status;

    @NotAudited    // exclude large or sensitive fields from audit log
    private byte[] attachmentData;
}

// Envers creates: order_AUD table
// | id | REV | REVTYPE | status |
// REVTYPE: 0=insert, 1=update, 2=delete

// Query: all versions of a specific order
AuditReader reader = AuditReaderFactory.get(em);
List<Number> revisions = reader.getRevisions(Order.class, orderId);
Order atRevision = reader.find(Order.class, orderId, revisions.get(0));

// Query: state of order at a specific timestamp
Order atTime = reader.find(Order.class, orderId,
    reader.getRevisionNumberForDate(targetDate));

Key Points to Remember

  • 1@EnableJpaAuditing must reference the AuditorAware bean by name via auditorAwareRef when multiple beans exist.
  • 2@EntityListeners(AuditingEntityListener.class) must be on the entity or its MappedSuperclass for fields to populate.
  • 3AuditorAware returns Optional.empty() for unauthenticated requests — @CreatedBy field will be left null.
  • 4@Column(updatable=false) on @CreatedDate and @CreatedBy prevents accidental overwrite on updates.
  • 5Hibernate Envers is the right tool for full change history; Spring Auditing only captures latest-modifier metadata.
  • 6For batch jobs or scheduled tasks without a SecurityContext, return "system" from AuditorAware as a fallback.

Interview Questions

Sign in to ask Aria
1

What annotations does Spring Data JPA Auditing require to automatically populate createdAt and updatedAt?

EasyAmazon
2

How does AuditorAware connect to Spring Security to get the current user?

MediumNetflix
3

What is the difference between Spring Data Auditing and Hibernate Envers?

MediumShopify
4

How would you populate audit fields in a batch job that runs without an authenticated user?

MediumZalando
5

How would you query Hibernate Envers to find what an entity looked like at a specific timestamp?

HardGoogle

Ask Aria about @Auditing with Spring Data

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…