In this post, we’ll see how to use JaVers in a simple Spring Boot app and MongoDB environment to track changes.
· Prerequisites
· Overview
∘ What is JaVers
∘ Why use JaVers
· Spring Boot Project Set-up
∘ JaVers Dependencies
∘ JaversRepository Configuration
∘ Code implementation
· Testing
· Author Provider
· Retrieving Audit Information From JaVers Snapshots Repository
· Conclusion
· References
Prerequisites
This is the list of all the prerequisites:
- Spring Boot 3+
- Maven 3.6.+
- Java 17 or later
- MongoDB instance installed
Overview
What is JaVers
JaVers is a lightweight, fully open-source Java library for auditing changes in application data.
The usage of this tool is not limited to debugging and auditing only. It can be successfully applied to perform analysis, force security policies, and maintain the event log.
Why use JaVers
- It’s open-source and free to use.
- Configuration is easy.
- It is compatible with traditional relational database systems and NoSQL systems. This makes migration between RDBMS and NoSQL much smoother.
- Easy integration with Spring and Spring Boot.
Spring Boot Project Set-up
We will start by creating a simple Spring Boot project from start.spring.io, with the following dependencies: Spring Web, Spring Data MongoDB, Lombok, and Validation.
JaVers Dependencies
To integrate JaVers into our application, we must add the JaVers Spring Boot starter dependency in the project’s build.gradle or pom.xml file.
<dependency>
<groupId>org.javers</groupId>
<artifactId>javers-spring-boot-starter-mongo</artifactId>
<version>7.3.8</version>
</dependency>
compile 'org.javers:javers-spring-boot-starter-mongo:7.3.8'
Javers 7.x is compatible with Spring Boot 3, so all Javers Spring integration modules require Java 17.
JaversRepository Configuration
By default, the Javers MongoDB starter creates a MongoRepository instance connected to the application’s database, which is managed by the Spring MongoDB starter.
We will add the MongoRepository properties with default values in the application.yml file
javers:
documentDbCompatibilityEnabled: false
objectAccessHook: org.javers.spring.mongodb.DBRefUnproxyObjectAccessHook
snapshotsCacheSize: 5000
snapshotCollectionName: "jv_snapshots"
headCollectionName: "jv_head_id"
schemaManagementEnabled: true
Schema
JaVers creates two collections in MongoDB:
jv_head_id— one document with the last CommitId,jv_snapshots— domain object snapshots. Each document contains snapshot data and commit metadata.
Dedicated MongoDB database for Javers
Optionally, JaVers starters rely on Spring Data starters. it offers an option to use a dedicated Mongo database for JaVers data.
javers:
mongodb:
host: localhost
port: 27017
authentication-database: admin
database: <<databasemane>>
username: <<dbuser>>
password: <<dbpassword>>
If javers.mongodb property is defined, as either host or uri has to be set. If so, an application’s data and JaVers data are stored in different databases.
Code implementation
Employee Class
Let’s define the Employe collection like this:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@Document(collection = "employee")
public class Employee {
@Id
private String id;
private String firstName;
private String lastName;
private String email;
}
Employee Repository
JaVers is based on annotations: @JaversSpringDataAuditable and @JaversAuditable.
In our case, we are using Spring Data, so we just need to annotate the employee repository with the JaversSpringDataAuditable annotation.
@Repository
@JaversSpringDataAuditable
public interface EmployeeRepository extends MongoRepository<Employee, String> {
}
By adding this annotation, all objects passed to save() and delete() methods will be automatically audited by JaVers.
In the case of a custom repository (non-Spring Data), annotate all data-changing methods with JaversAuditable annotation.
@Repository
class EmployeeRepository {
@JaversAuditable
public void save(Employee employee) {
...//
}
public Employee find(String id) {
...//
}
@JaversAuditable
public void update(Employee employee) {
...//
}
}
EmployeeController class
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@GetMapping()
public List<Employee> getAll() {
return employeeService.getAll();
}
@PostMapping()
public Employee save(@RequestBody Employee employee) {
return employeeService.save(employee);
}
@PutMapping(value = "/{id}")
public Employee update(@PathVariable("id") String id, @RequestBody Employee employee) {
Optional<Employee> emp = employeeService.getById(id);
if (emp.isPresent()) {
emp.get().setFirstName(employee.getFirstName());
emp.get().setLastName(employee.getLastName());
emp.get().setEmail(employee.getEmail());
return employeeService.save(emp.get());
}
throw new RuntimeException("not found");
}
@GetMapping("/{id}")
public Employee getEmployeeById(@PathVariable(value = "id") String id) {
return employeeService.getById(id).orElseThrow(() -> new RuntimeException("not found"));
}
@DeleteMapping("/{id}")
public void deleteEmployee(@PathVariable(value = "id") String id) {
employeeService.deleteById(id);
}
}
Testing
Now let’s try to change the data. for this, we use the Postman tool to run the endpoints.

We noticed that JaVers added two new collections in our MongoDB database after saving the employee document.

In the jv_snapshots collection, there are three types of documents for auditing a document.
- INITIAL: Create operation in the database.
- UPDATE: Update operation.
- TERMINATE: Delete operation has been performed.
When creating the employee document, we can see in jv_snapshots:

- commitMetadata: This includes valuable information such as the author of the change, the time of the event in local (commitDate) and UTC (commitDateInstant).
- globalId: Contains information about the entity being modified and its database ID.
- state: Contains all the field values of the entity being modified. Please note that this is the state of the object after the change has been performed.
- changedProperties: This includes the fields that have been modified in this audit log entry. Since this is a creation operation, all fields have been included in the changed properties array.
Let’s start the update operation:

We have a new document has been created with type UPDATE and the version has been incremented to 2.
Finally, let’s run the delete operation:

Since the object has been deleted, the entry log type here is TERMINATE. Notice that the changed properties array is empty.
Author Provider
Each committed change in JaVers should have its author. i.e. the user who made the change. Moreover, JaVers supports Spring Security out of the box. We need to register an implementation of the AuthorProvider interface, which should return a current user name.
In this story, we created a simple custom implementation of the AuthorProvider interface.
@Configuration
public class JaversConfiguration {
private static final String AUTHOR = "aek author";
@Bean
public AuthorProvider provideJaversAuthor() {
return new SimpleAuthorProvider();
}
private static class SimpleAuthorProvider implements AuthorProvider {
@Override
public String provide() {
return AUTHOR;
}
}
}
Retrieving Audit Information From JaVers Snapshots Repository
JaVers provides its own JaVers Query Language (JQL). It is a simple, fluent API that allows you to query JaversRepository for changes in a given class, object, or property.
Data history can be fetched from JaversRepository using javers.find*() methods in one of three views: Shadows, Changes, and Snapshots.
- Shadow is a historical version of a domain object restored from a snapshot.
- Change represents an atomic difference between two objects.
- Snapshot is the historical state of a domain object captured as the
property:valuemap.
Let’s take a look at some examples:
When we want to retrieve all the changes made to the Employee collection.
curl --location --request GET 'http://localhost:8080/audit/employees'
<pre>Changes:
Commit 3.00 done by aek author at 10 Mar 2024, 17:31:17 :
* object removed: com.java.audit.springdatamongojavers.domain.Employee/65eddc0d8918bd28ed76f92e
- 'email' value 'kd.willi@gmail.com' unset
- 'firstName' value 'Karl Williams' unset
- 'id' value '65eddc0d8918bd28ed76f92e' unset
- 'lastName' value 'Kora D' unset
Commit 2.00 done by aek author at 10 Mar 2024, 17:23:56 :
* changes on com.java.audit.springdatamongojavers.domain.Employee/65eddc0d8918bd28ed76f92e :
- 'email' changed: 'k.willi@gmail.com' -> 'kd.willi@gmail.com'
- 'firstName' changed: 'Williams' -> 'Karl Williams'
- 'lastName' changed: 'Kora' -> 'Kora D'
Commit 1.00 done by aek author at 10 Mar 2024, 17:13:01 :
* new object: com.java.audit.springdatamongojavers.domain.Employee/65eddc0d8918bd28ed76f92e
- 'email' = 'k.willi@gmail.com'
- 'firstName' = 'Williams'
- 'id' = '65eddc0d8918bd28ed76f92e'
- 'lastName' = 'Kora'
</pre>
Properties are matched by name, and their values are compared, without paying much attention to the actual Employee class.
Conclusion
Well done !!. In this post, we’ve learned how to use JaVers in the Spring boot application with Spring Data MongoDB.
JaVers can have different applications, from debugging to complex analysis. The official documentation presents several examples of use. Currently, It supports the following databases: MongoDB, H2, PostgreSQL, MySQL, MariaDB, Oracle, and Microsoft SQL Server.
The complete source code is available on GitHub.
This story is an improved version of the original version that I published on dzone.com
You can reach out to me and follow me on Medium, Twitter, GitHub
Thanks for reading!