1. Overview

In this article, we will be looking at the JaVers library.

This library helps programmers examine and detect changes in the states of simple Java objects. When we use mutable objects in our code, every object can potentially be modified in various places in the application; JaVers would help us discover and audit these changes.

2. Maven Dependency

To get started let us add the javers-core Maven dependency to our pom.xml:

<dependency>
    <groupId>org.javers</groupId>
    <artifactId>javers-core</artifactId>
    <version>3.1.0</version>
</dependency>

We can find the latest version over on Maven Central.

3. Detecting POJO State Changes

Let's start with a simple Person class:

public class Person {
    private Integer id;
    private String name;

    // standard getters/constructors
}

Suppose that we created a Person object in one part of our application, and in some other part of the codebase, the name of the person with the same id field was changed. We want to compare them to find out what kind of changes happened to the person object.

We can compare those two objects using the compare() method from the JaVers class:

@Test
public void givenPersonObject_whenApplyModificationOnIt_thenShouldDetectChange() {
    // given
    Javers javers = JaversBuilder.javers().build();

    Person person = new Person(1, "Michael Program");
    Person personAfterModification = new Person(1, "Michael Java");

    // when
    Diff diff = javers.compare(person, personAfterModification);

    // then
    ValueChange change = diff.getChangesByType(ValueChange.class).get(0);

    assertThat(diff.getChanges()).hasSize(1);
    assertThat(change.getPropertyName()).isEqualTo("name");
    assertThat(change.getLeft()).isEqualTo("Michael Program");
    assertThat(change.getRight()).isEqualTo("Michael Java");
}

4. Detecting State Change of List of Objects

If we're working with collections of objects, we similarly need to examine state changes by looking at the each element in the collection. Sometimes, we want to add or remove the particular object from the list, altering its state.

Let's have a look at an example; say we have a list of objects, and we remove one object from that list.

That change can be undesirable for some reason, and we want to audit changes that occurred in this list. JaVers allows us to do it by using a compareCollections() method:

@Test
public void givenListOfPersons_whenCompare_ThenShouldDetectChanges() {
    // given
    Javers javers = JaversBuilder.javers().build();
    Person personThatWillBeRemoved = new Person(2, "Thomas Link");
    List<Person> oldList = 
      Lists.asList(new Person(1, "Michael Program"), personThatWillBeRemoved);
    List<Person> newList = 
      Lists.asList(new Person(1, "Michael Not Program"));

    // when
    Diff diff = javers.compareCollections(oldList, newList, Person.class);

    // then
    assertThat(diff.getChanges()).hasSize(3);

    ValueChange valueChange = 
      diff.getChangesByType(ValueChange.class).get(0);
 
    assertThat(valueChange.getPropertyName()).isEqualTo("name");
    assertThat(valueChange.getLeft()).isEqualTo("Michael Program");
    assertThat(valueChange.getRight()).isEqualTo("Michael Not Program");

    ObjectRemoved objectRemoved = diff.getChangesByType(ObjectRemoved.class).get(0);
    assertThat(
      objectRemoved.getAffectedObject().get().equals(personThatWillBeRemoved))
      .isTrue();

    ListChange listChange = diff.getChangesByType(ListChange.class).get(0);
    assertThat(listChange.getValueRemovedChanges().size()).isEqualTo(1);
}

5. Comparing Object Graphs

In real word applications, we often deal with the object graphs. Let's say that we have a PersonWithAddress class that has a list of the Address objects and we are adding a new address for the given person.

We can easily find the type of change that has occurred:

@Test
public void givenListOfPerson_whenPersonHasNewAddress_thenDetectThatChange() {
    // given
    Javers javers = JaversBuilder.javers().build();

    PersonWithAddress person = 
      new PersonWithAddress(1, "Tom", Arrays.asList(new Address("England")));

    PersonWithAddress personWithNewAddress = 
      new PersonWithAddress(1, "Tom", 
        Arrays.asList(new Address("England"), new Address("USA")));


    // when
    Diff diff = javers.compare(person, personWithNewAddress);
    List objectsByChangeType = diff.getObjectsByChangeType(NewObject.class);

    // then
    assertThat(objectsByChangeType).hasSize(1);
    assertThat(objectsByChangeType.get(0).equals(new Address("USA")));
}

Similarly, removing an address will be detected:

@Test
public void givenListOfPerson_whenPersonRemovedAddress_thenDetectThatChange() {
    // given
    Javers javers = JaversBuilder.javers().build();

    PersonWithAddress person = 
      new PersonWithAddress(1, "Tom", Arrays.asList(new Address("England")));

    PersonWithAddress personWithNewAddress = 
      new PersonWithAddress(1, "Tom", Collections.emptyList());


    // when
    Diff diff = javers.compare(person, personWithNewAddress);
    List objectsByChangeType = diff.getObjectsByChangeType(ObjectRemoved.class);

    // then
    assertThat(objectsByChangeType).hasSize(1);
    assertThat(objectsByChangeType.get(0).equals(new Address("England")));
}

6. Conclusion

In this quick article, we used the JaVers library, a useful library that gives us APIs for detecting state changes in our objects. Not only it can detect the change in a simple POJO object, but also it can detect more complex shifts in the collections of objects, or even object graphs.

As always, the code is available over on GitHub.