diff --git a/persistence-modules/pom.xml b/persistence-modules/pom.xml
index 6609e619bf6c..79c485d55b96 100644
--- a/persistence-modules/pom.xml
+++ b/persistence-modules/pom.xml
@@ -139,6 +139,7 @@
hibernate-annotations-2
hibernate-reactive
my-sql
+ spring-data-envers
diff --git a/persistence-modules/spring-data-envers/pom.xml b/persistence-modules/spring-data-envers/pom.xml
new file mode 100644
index 000000000000..3fa1141bd26a
--- /dev/null
+++ b/persistence-modules/spring-data-envers/pom.xml
@@ -0,0 +1,61 @@
+
+
+ 4.0.0
+ spring-data-envers
+ spring-data-envers
+
+
+ com.baeldung
+ parent-boot-3
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-3
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.data
+ spring-data-envers
+
+
+ com.h2database
+ h2
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.xmlunit
+ xmlunit-core
+
+
+
+
+
+
+
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/CustomAuditApplication.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/CustomAuditApplication.java
new file mode 100644
index 000000000000..150f0dcf8477
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/CustomAuditApplication.java
@@ -0,0 +1,7 @@
+package com.baeldung.envers.customrevision;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CustomAuditApplication {
+}
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/CustomRevisionEntity.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/CustomRevisionEntity.java
new file mode 100644
index 000000000000..f5652d5aa4af
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/CustomRevisionEntity.java
@@ -0,0 +1,21 @@
+package com.baeldung.envers.customrevision.domain;
+
+import com.baeldung.envers.customrevision.service.CustomRevisionListener;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EntityListeners;
+import lombok.*;
+import org.hibernate.envers.DefaultRevisionEntity;
+import org.hibernate.envers.RevisionEntity;
+
+@Entity
+@RevisionEntity
+@EqualsAndHashCode(callSuper = true)
+@Getter
+@Setter
+@NoArgsConstructor
+@EntityListeners(CustomRevisionListener.class)
+public class CustomRevisionEntity extends DefaultRevisionEntity {
+
+ private String remoteHost;
+ private String remoteUser;
+}
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/Owner.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/Owner.java
new file mode 100644
index 000000000000..07816a7b7475
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/Owner.java
@@ -0,0 +1,30 @@
+package com.baeldung.envers.customrevision.domain;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import org.hibernate.envers.Audited;
+
+import java.util.List;
+
+@Entity
+@Audited
+@Data
+public class Owner {
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ @Column(nullable = false)
+ private String name;
+
+ @OneToMany(fetch = FetchType.EAGER)
+ private List pets;
+
+ public static Owner forName(String name ) {
+
+ var owner = new Owner();
+ owner.setName(name);
+ return owner;
+ }
+
+}
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/Pet.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/Pet.java
new file mode 100644
index 000000000000..4f61fc07ea9c
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/Pet.java
@@ -0,0 +1,31 @@
+package com.baeldung.envers.customrevision.domain;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import org.hibernate.envers.Audited;
+
+import java.time.LocalDate;
+import java.util.UUID;
+
+@Entity
+@Audited
+@Data
+public class Pet {
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ @Column(unique = true, nullable = false)
+ private UUID uuid;
+
+ private String name;
+
+ // A null ownes implies the pet is available for adoption
+ @ManyToOne
+ @JoinColumn(name = "owner_id", nullable = true)
+ private Owner owner;
+
+ @ManyToOne
+ @JoinColumn(name = "species_id")
+ private Species species;
+}
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/PetHistoryEntry.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/PetHistoryEntry.java
new file mode 100644
index 000000000000..27703ec3d439
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/PetHistoryEntry.java
@@ -0,0 +1,18 @@
+package com.baeldung.envers.customrevision.domain;
+
+import org.springframework.data.history.RevisionMetadata;
+
+import java.time.Instant;
+import java.util.UUID;
+
+public record PetHistoryEntry(
+ Instant eventDate,
+ RevisionMetadata.RevisionType revisionType,
+ UUID petUuid,
+ String speciesName,
+ String petName,
+ String ownerName,
+ String remoteHost,
+ String remoteUser
+) {
+}
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/PetLogInfo.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/PetLogInfo.java
new file mode 100644
index 000000000000..8b5f3a7bfbc1
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/PetLogInfo.java
@@ -0,0 +1,16 @@
+package com.baeldung.envers.customrevision.domain;
+
+import org.springframework.data.history.RevisionMetadata;
+
+import java.time.Instant;
+import java.util.UUID;
+
+public record PetLogInfo(
+ Instant eventDate,
+ RevisionMetadata.RevisionType revisionType,
+ UUID petUuid,
+ String speciesName,
+ String petName,
+ String ownerName
+) {
+}
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/Species.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/Species.java
new file mode 100644
index 000000000000..bfcadb7765a1
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/domain/Species.java
@@ -0,0 +1,25 @@
+package com.baeldung.envers.customrevision.domain;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.envers.Audited;
+
+@Entity
+@Audited
+@Data
+@NoArgsConstructor
+public class Species {
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ @Column(unique = true)
+ private String name;
+
+ public static Species forName(String name) {
+ var s = new Species();
+ s.setName(name);
+ return s;
+ }
+}
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/repository/OwnerRepository.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/repository/OwnerRepository.java
new file mode 100644
index 000000000000..8ad3952d4d40
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/repository/OwnerRepository.java
@@ -0,0 +1,10 @@
+package com.baeldung.envers.customrevision.repository;
+
+import com.baeldung.envers.customrevision.domain.Owner;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface OwnerRepository extends JpaRepository {
+ Optional findByName(String name);
+}
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/repository/PetRepository.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/repository/PetRepository.java
new file mode 100644
index 000000000000..87761a82702e
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/repository/PetRepository.java
@@ -0,0 +1,15 @@
+package com.baeldung.envers.customrevision.repository;
+
+import com.baeldung.envers.customrevision.domain.Pet;
+import com.baeldung.envers.customrevision.domain.Species;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.repository.history.RevisionRepository;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+public interface PetRepository extends JpaRepository, RevisionRepository {
+ List findPetsByOwnerNullAndSpecies(Species species);
+ Optional findPetByUuid(UUID uuid);
+}
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/repository/SpeciesRepository.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/repository/SpeciesRepository.java
new file mode 100644
index 000000000000..9ba9b8093569
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/repository/SpeciesRepository.java
@@ -0,0 +1,11 @@
+package com.baeldung.envers.customrevision.repository;
+
+import com.baeldung.envers.customrevision.domain.Species;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface SpeciesRepository extends JpaRepository {
+
+ Optional findByName(String name);
+}
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/service/AdoptionService.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/service/AdoptionService.java
new file mode 100644
index 000000000000..40364d395c57
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/service/AdoptionService.java
@@ -0,0 +1,99 @@
+package com.baeldung.envers.customrevision.service;
+
+
+import com.baeldung.envers.customrevision.domain.CustomRevisionEntity;
+import com.baeldung.envers.customrevision.domain.Pet;
+import com.baeldung.envers.customrevision.domain.PetHistoryEntry;
+import com.baeldung.envers.customrevision.repository.OwnerRepository;
+import com.baeldung.envers.customrevision.repository.PetRepository;
+import com.baeldung.envers.customrevision.repository.SpeciesRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.UUID;
+
+@Service
+@RequiredArgsConstructor
+public class AdoptionService {
+
+ private final OwnerRepository ownersRepo;
+ private final PetRepository petsRepo;
+ private final SpeciesRepository speciesRepo;
+
+ /**
+ * Register a new pet for adoption. Initially, this pet will have no name or owner.
+ * @param speciesName
+ * @return The assigned UUID for this pet
+ */
+ public UUID registerForAdoption( String speciesName) {
+
+ var species = speciesRepo.findByName(speciesName)
+ .orElseThrow(() -> new IllegalArgumentException("Unknown Species: " + speciesName));
+
+ var pet = new Pet();
+ pet.setSpecies(species);
+ pet.setUuid(UUID.randomUUID());
+ petsRepo.save(pet);
+ return pet.getUuid();
+ }
+
+
+ public List findPetsForAdoption(String speciesName) {
+ var species = speciesRepo.findByName(speciesName)
+ .orElseThrow(() -> new IllegalArgumentException("Unknown Species: " + speciesName));
+
+ return petsRepo.findPetsByOwnerNullAndSpecies(species);
+ }
+
+ public Pet adoptPet(UUID petUuid, String ownerName, String petName) {
+
+ var newOwner = ownersRepo.findByName(ownerName)
+ .orElseThrow(() -> new IllegalArgumentException("Unknown owner"));
+
+ var pet = petsRepo.findPetByUuid(petUuid)
+ .orElseThrow(() -> new IllegalArgumentException("Unknown pet"));
+
+ if ( pet.getOwner() != null) {
+ throw new IllegalArgumentException("Pet already adopted");
+ }
+
+ pet.setOwner(newOwner);
+ pet.setName(petName);
+ petsRepo.save(pet);
+ return pet;
+ }
+
+ public Pet returnPet(UUID petUuid) {
+ var pet = petsRepo.findPetByUuid(petUuid)
+ .orElseThrow(() -> new IllegalArgumentException("Unknown pet"));
+
+ pet.setOwner(null);
+ petsRepo.save(pet);
+ return pet;
+ }
+
+
+ public List listPetHistory(UUID petUuid) {
+
+ var pet = petsRepo.findPetByUuid(petUuid)
+ .orElseThrow(() -> new IllegalArgumentException("No pet with UUID '" + petUuid + "' found"));
+
+ return petsRepo.findRevisions(pet.getId()).stream()
+ .map(r -> {
+ CustomRevisionEntity rev = r.getMetadata().getDelegate();
+ return new PetHistoryEntry(r.getRequiredRevisionInstant(),
+ r.getMetadata().getRevisionType(),
+ r.getEntity().getUuid(),
+ r.getEntity().getSpecies().getName(),
+ r.getEntity().getName(),
+ r.getEntity().getOwner() != null ? r.getEntity().getOwner().getName() : null,
+ rev.getRemoteHost(),
+ rev.getRemoteUser());
+ })
+ .toList();
+
+ }
+
+
+}
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/service/CustomRevisionListener.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/service/CustomRevisionListener.java
new file mode 100644
index 000000000000..190161cf4724
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/service/CustomRevisionListener.java
@@ -0,0 +1,29 @@
+package com.baeldung.envers.customrevision.service;
+
+import com.baeldung.envers.customrevision.domain.CustomRevisionEntity;
+import jakarta.persistence.PrePersist;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+import java.util.function.Supplier;
+
+@Component
+@RequiredArgsConstructor
+public class CustomRevisionListener {
+
+ private final Supplier> requestInfoSupplier;
+
+ @PrePersist
+ private void onPersist(CustomRevisionEntity entity) {
+
+ var info = requestInfoSupplier.get();
+ if ( info.isEmpty()) {
+ return;
+ }
+
+ entity.setRemoteHost(info.get().remoteHost());
+ entity.setRemoteUser(info.get().remoteUser());
+
+ }
+}
diff --git a/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/service/RequestInfo.java b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/service/RequestInfo.java
new file mode 100644
index 000000000000..95bd9c1e0959
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/main/java/com/baeldung/envers/customrevision/service/RequestInfo.java
@@ -0,0 +1,4 @@
+package com.baeldung.envers.customrevision.service;
+
+public record RequestInfo(String remoteHost, String remoteUser) {
+}
diff --git a/persistence-modules/spring-data-envers/src/main/resources/application.properties b/persistence-modules/spring-data-envers/src/main/resources/application.properties
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/persistence-modules/spring-data-envers/src/test/java/com/baeldung/envers/customrevision/CustomAuditApplicationUnitTest.java b/persistence-modules/spring-data-envers/src/test/java/com/baeldung/envers/customrevision/CustomAuditApplicationUnitTest.java
new file mode 100644
index 000000000000..55ccf0e92ea4
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/test/java/com/baeldung/envers/customrevision/CustomAuditApplicationUnitTest.java
@@ -0,0 +1,89 @@
+package com.baeldung.envers.customrevision;
+
+import com.baeldung.envers.customrevision.domain.Owner;
+import com.baeldung.envers.customrevision.domain.PetHistoryEntry;
+import com.baeldung.envers.customrevision.domain.Species;
+import com.baeldung.envers.customrevision.repository.OwnerRepository;
+import com.baeldung.envers.customrevision.repository.SpeciesRepository;
+import com.baeldung.envers.customrevision.service.AdoptionService;
+import com.baeldung.envers.customrevision.service.RequestInfo;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@Slf4j
+@SpringBootTest
+public class CustomAuditApplicationUnitTest {
+
+ @Autowired
+ AdoptionService adoptionService;
+
+ @Test
+ void whenRegisterForAdoption_thenSuccess() {
+
+ var generatedUuid = adoptionService.registerForAdoption("dog");
+ assertNotNull(generatedUuid);
+
+ var doggies = adoptionService.findPetsForAdoption("dog");
+ assertNotNull(doggies);
+ assertEquals(1,doggies.size(), "should have one dog for adoption");
+ assertEquals("dog", doggies.get(0).getSpecies().getName());
+ assertNull(doggies.get(0).getOwner());
+ }
+
+ @Test
+ void whenAdoptPet_thenSuccess() {
+
+ var petUuid = adoptionService.registerForAdoption("cat");
+ var kitty = adoptionService.adoptPet(petUuid,"adam", "kitty");
+
+ List kittyHistory = adoptionService.listPetHistory(kitty.getUuid());
+ assertNotNull(kittyHistory);
+ assertTrue(kittyHistory.size() > 0 , "kitty should have a history");
+ for (PetHistoryEntry e : kittyHistory) {
+ log.info("Entry: {}", e);
+ }
+
+ }
+
+ @TestConfiguration
+ static class TestConfig {
+
+ @Bean
+ Supplier> requestInfoSupplier() {
+
+ return () -> Optional.of(new RequestInfo("example.com", "thomas"));
+
+ }
+
+ @Bean
+ CommandLineRunner populateShelter(SpeciesRepository speciesRepo, OwnerRepository ownerRepo) {
+
+ return (args) -> {
+ // Add species
+ speciesRepo.saveAll(List.of(
+ Species.forName("dog"),
+ Species.forName("cat"),
+ Species.forName("chinchilla")));
+
+ // Add Owners
+ ownerRepo.saveAll( List.of(
+ Owner.forName("adam"),
+ Owner.forName("mary"),
+ Owner.forName("phil")
+ ));
+ };
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-data-envers/src/test/resources/application.properties b/persistence-modules/spring-data-envers/src/test/resources/application.properties
new file mode 100644
index 000000000000..bd3d32502a3f
--- /dev/null
+++ b/persistence-modules/spring-data-envers/src/test/resources/application.properties
@@ -0,0 +1,2 @@
+spring.jpa.hibernate.ddl-auto=create
+logging.level.sql=TRACE
\ No newline at end of file