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
Compliance and Legal Requirements
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
- Always remember to add
@EnableJpaAuditing
to your configuration. Without it, the audit annotations won’t work. - Don’t forget
@EntityListeners(AuditingEntityListener.class)
on your auditable entities or base class. - Always handle cases where there might not be an authenticated user, especially in background jobs or system operations.
- Be consistent with your time zone handling. Consider using UTC for audit timestamps and converting to local time zones only for display.
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.