Skip to content

Always Set Up AuditorAware in Spring JPA

Published: at 08:00 AM

Brief

My goal is to make posts like this the SIMPLEST place on the internet to learn how to do things that caused me trouble. That way, if this is found, someone doesn’t have to do the same digging I had to do.

In every Spring Boot project I work on, one of the first things I do after setting up JPA is configure AuditorAware. This post covers why this should be a standard practice and exactly how to set it up.

Why You Should Always Set This Up

Many industries require detailed audit trails. Whether you’re working in finance, healthcare, or any regulated industry, knowing who modified what and when isn’t just nice to have—it’s mandatory. Setting up auditing from day one saves you from painful retrofitting later.

Debugging and Troubleshooting

When production issues arise, audit fields become your best friend. Being able to trace changes back to specific users can cut debugging time from hours to minutes. I’ve saved countless hours by having this information readily available.

Security and Accountability

In today’s security-conscious world, having a clear trail of who made changes is crucial for incident response and forensic analysis. It’s also a deterrent against malicious behavior when users know their actions are tracked.

Zero Code Overhead

Once configured, JPA auditing works automatically. You don’t need to remember to set these fields manually—Spring handles it transparently. This eliminates human error and ensures consistency across your entire application.

The Setup

Here’s everything you need to get AuditorAware working with Spring JPA!

Dependencies

First, ensure you have the necessary dependencies in your build.gradle or pom.xml:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'
}

Enable JPA Auditing

Add the @EnableJpaAuditing annotation to your main application class:

@SpringBootApplication
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Create an Auditable Base Entity

Create a base entity that other entities can extend:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AuditableEntity {
    
    @CreatedDate
    @Column(name = "created_date", nullable = false, updatable = false)
    private LocalDateTime createdDate;
    
    @LastModifiedDate
    @Column(name = "last_modified_date")
    private LocalDateTime lastModifiedDate;
    
    @CreatedBy
    @Column(name = "created_by", updatable = false)
    private String createdBy;
    
    @LastModifiedBy
    @Column(name = "last_modified_by")
    private String lastModifiedBy;
    
    // Getters and setters
    public LocalDateTime getCreatedDate() {
        return createdDate;
    }
    
    public void setCreatedDate(LocalDateTime createdDate) {
        this.createdDate = createdDate;
    }
    
    public LocalDateTime getLastModifiedDate() {
        return lastModifiedDate;
    }
    
    public void setLastModifiedDate(LocalDateTime lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }
    
    public String getCreatedBy() {
        return createdBy;
    }
    
    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }
    
    public String getLastModifiedBy() {
        return lastModifiedBy;
    }
    
    public void setLastModifiedBy(String lastModifiedBy) {
        this.lastModifiedBy = lastModifiedBy;
    }
}

Implement AuditorAware

Create an implementation of AuditorAware that returns the current user:

@Component("auditorProvider")
public class SpringSecurityAuditorAware implements AuditorAware<String> {
    
    @Override
    public Optional<String> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        if (authentication == null || 
            !authentication.isAuthenticated() || 
            authentication instanceof AnonymousAuthenticationToken) {
            return Optional.of("system");
        }
        
        return Optional.of(authentication.getName());
    }
}

Use the Auditable Base Entity

Now, extend your entities from the auditable base:

@Entity
@Table(name = "users")
public class User extends AuditableEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String email;
    
    @Column(nullable = false)
    private String firstName;
    
    @Column(nullable = false)
    private String lastName;
    
    // Constructors, getters, and setters
}

That’s it! Now every time you save or update a User entity, the audit fields will be automatically populated.

Advanced Use Cases

Using UUID for User Identification

For better security and to avoid exposing usernames, you might want to use UUIDs:

@Component("auditorProvider")
public class UuidAuditorAware implements AuditorAware<UUID> {
    
    @Override
    public Optional<UUID> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        if (authentication == null || !authentication.isAuthenticated()) {
            return Optional.empty();
        }
        
        // Assuming your UserPrincipal has a getUserId() method
        if (authentication.getPrincipal() instanceof UserPrincipal) {
            UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
            return Optional.of(userPrincipal.getUserId());
        }
        
        return Optional.empty();
    }
}

Custom Audit Fields

Sometimes you need additional audit information like IP addresses:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class ExtendedAuditableEntity {
    
    // Standard audit fields
    @CreatedDate
    private LocalDateTime createdDate;
    
    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
    
    @CreatedBy
    private String createdBy;
    
    @LastModifiedBy
    private String lastModifiedBy;
    
    // Additional audit fields
    @Column(name = "created_from_ip")
    private String createdFromIp;
    
    @Column(name = "last_modified_from_ip")
    private String lastModifiedFromIp;
    
    @PrePersist
    protected void onCreate() {
        this.createdFromIp = getCurrentUserIp();
    }
    
    @PreUpdate
    protected void onUpdate() {
        this.lastModifiedFromIp = getCurrentUserIp();
    }
    
    private String getCurrentUserIp() {
        // Implementation to get current user's IP address
        // This could be from request context or a custom service
        return "127.0.0.1"; // Placeholder
    }
}

Testing Your Setup

Here’s how to test that your auditing is working correctly:

@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class AuditingTest {
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    @WithMockUser(username = "testuser")
    void testAuditFieldsArePopulated() {
        // Given
        User user = new User();
        user.setEmail("test@example.com");
        user.setFirstName("John");
        user.setLastName("Doe");
        
        // When
        User savedUser = userRepository.save(user);
        
        // Then
        assertThat(savedUser.getCreatedDate()).isNotNull();
        assertThat(savedUser.getCreatedBy()).isEqualTo("testuser");
        assertThat(savedUser.getLastModifiedDate()).isNotNull();
        assertThat(savedUser.getLastModifiedBy()).isEqualTo("testuser");
    }
}

Common Gotchas

Wrapping Up

Setting up AuditorAware in Spring JPA is a small upfront investment that pays huge dividends throughout the life of your application. It provides essential audit trails, improves debugging capabilities, and ensures compliance with various regulatory requirements.

The setup is straightforward, the maintenance overhead is minimal, and the benefits are substantial. Make AuditorAware setup a standard part of your Spring Boot project initialization checklist. Your future self (and your team) will thank you for it.


Previous Post
Building a Website Purely with Vibes
Next Post
JLink - Java Runtime Optimization Reference Guide