Diff Examples

All examples are runnable. Checkout our github repository:

git clone https://github.com/javers/javers.git
cd javers

Run examples as unit tests:

./gradlew javers-core:test -Dtest.single=BasicEntityDiffExample

Compare two Entity objects

Let’s start with something simple. This example shows how to find a diff between two objects of the Employee class. Every employee has his own identity, so the Employee class is mapped as Entity. Our employee has some basic properties, collections, and references. Just the usual stuff.

The case
We have two objects, frodoOld and frodoNew. These objects represent two versions of the same being (a person called Frodo). To find out what’s changed, just call:

    javers.compare(frodoOld, frodoNew)

Configuration
JaVers needs to know that the Employee class is an Entity and the Address class is a Value Object. It’s enough to annotate the name field with the @Id annotation to map Employee as Entity. Address is mapped as Value Object by default. See domain-model-mapping for more details about JaVers’ type system.

What’s important
Notice that both objects have the same Id value — 'Frodo'. That’s why they are matched and compared. JaVers matches only objects with the same GlobalId. In this case, the GlobalId value is: 'Employee/Frodo'. Without the @TypeName annotation, it would be 'org.javers.core.examples.model.Employee/frodo'.

Employee.java:

@TypeName("Employee")
public class Employee {

    @Id
    private String name;

    private Position position;

    private int salary;

    private int age;

    private Employee boss;

    private List<Employee> subordinates = new ArrayList<>();

    private Address primaryAddress;

    private Set<String> skills;

    ... // omitted
}

Address.java:

public class Address {
    private String city;

    private String street;

    ... // omitted
}

BasicEntityDiffExample.java:

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

  Employee frodoOld = EmployeeBuilder.Employee("Frodo")
          .withAge(40)
          .withPosition("Townsman")
          .withSalary(10_000)
          .withPrimaryAddress(new Address("Shire"))
          .withSkills("management")
          .withSubordinates(new Employee("Sam"))
          .build();

  Employee frodoNew = EmployeeBuilder.Employee("Frodo")
          .withAge(41)
          .withPosition("Hero")
          .withBoss(new Employee("Gandalf"))
          .withPrimaryAddress(new Address("Mordor"))
          .withSalary(12_000)
          .withSkills("management", "agile coaching")
          .withSubordinates(new Employee("Sméagol"), new Employee("Sam"))
          .build();

  //when
  Diff diff = javers.compare(frodoOld, frodoNew);

  //then
  assertThat(diff.getChanges()).hasSize(9);
}

The resulting Diff is a container for the list of Changes.

There are three main types of Changes:

  • NewObject — when an object is present only in the right graph,
  • ObjectRemoved — when an object is present only in the left graph,
  • PropertyChange — most common — a changed property (field or getter).

PropertyChange has the following subtypes:

You can print the list of Changes using pretty toString():

System.out.println(diff);
Diff:
* new object: Employee/Sméagol
* new object: Employee/Gandalf
* changes on Employee/Frodo :
  - 'age' changed from '40' to '41'
  - 'boss' changed from '' to 'Employee/Gandalf'
  - 'position' changed from 'Townsman' to 'Hero'
  - 'primaryAddress.city' changed from 'Shire' to 'Mordor'
  - 'salary' changed from '10000' to '12000'
  - 'skills' collection changes :
    . 'agile coaching' added
  - 'subordinates' collection changes :
    0. 'Employee/Sméagol' added

Iterating over the list of Changes:

diff.getChanges().forEach(change -> System.out.println("- " + change));

Iterating over the list of Changes grouped by objects:

diff.groupByObject().forEach(byObject -> {
  System.out.println("* changes on " +byObject.getGlobalId().value() + " : ");
  byObject.get().forEach(change -> System.out.println("  - " + change));
});

Diff can be easily serialized to JSON:

System.out.println(javers.getJsonConverter().toJson(diff));
{
  "changes": [
    {
      "changeType": "NewObject",
      "globalId": {
        "entity": "Employee",
        "cdoId": "Gandalf"
      }
    },
    {
      "changeType": "NewObject",
      "globalId": {
        "entity": "Employee",
        "cdoId": "Sméagol"
      }
    },
    {
      "changeType": "ValueChange",
      "globalId": {
        "valueObject": "org.javers.core.examples.model.Address",
        "ownerId": {
          "entity": "Employee",
          "cdoId": "Frodo"
        },
        "fragment": "primaryAddress"
      },
      "property": "city",
      "left": "Shire",
      "right": "Mordor"
    },
    {
      "changeType": "ValueChange",
      "globalId": {
        "entity": "Employee",
        "cdoId": "Frodo"
      },
      "property": "position",
      "left": "Townsman",
      "right": "Hero"
    },
    ...
  ]
}

Compare graphs

JaVers can compare arbitrary complex structures of objects. In this example, we show how you can deeply compare employee hierarchies to detect specific types of changes.

For simplicity of this example, the data model is reduced to the one class — Employee (the same as in the previous example).

Conceptually, an employee hierarchy is a tree. Technically, we have a graph with cycles here (since the relationship between boss and employees is bidirectional).

The case
We are comparing two versions (historical states) of the employee hierarchy in order to detect the four types of changes:

  • employee hired (NewObject),
  • employee fired (ObjectRemoved),
  • salary change (ValueChange),
  • boss change (ReferenceChange).

Configuration
JaVers needs to know that Employee class is an Entity. It’s enough to annotate the name field with the @Id annotation.

What’s important
JaVers makes no assumptions about your data structures and treats them just like graphs with cycles (the same as JVM does). There are no limitations on the number of nodes in the graph.

shouldDetectHired():

  /** {@link NewObject} example */
  @Test
  public void shouldDetectHired() {
    //given
    Javers javers = JaversBuilder.javers().build();

    Employee oldBoss = new Employee("Big Boss")
        .addSubordinates(
            new Employee("Great Developer"));

    Employee newBoss = new Employee("Big Boss")
        .addSubordinates(
            new Employee("Great Developer"),
            new Employee("Hired One"),
            new Employee("Hired Second"));

    //when
    Diff diff = javers.compare(oldBoss, newBoss);

    //then
    assertThat(diff.getObjectsByChangeType(NewObject.class))
        .hasSize(2)
        .containsOnly(new Employee("Hired One"),
                      new Employee("Hired Second"));

    System.out.println(diff);
  }
Diff:
* new object: Employee/Hired One
* new object: Employee/Hired Second
* changes on Employee/Big Boss :
  - 'subordinates' collection changes :
    1. 'Employee/Hired One' added
    2. 'Employee/Hired Second' added  

shouldDetectFired():

  /** {@link ObjectRemoved} example */
  @Test
  public void shouldDetectFired() {
    //given
    Javers javers = JaversBuilder.javers().build();

    Employee oldBoss = new Employee("Big Boss")
            .addSubordinates(
                    new Employee("Great Developer"),
                    new Employee("Team Lead").addSubordinates(
                            new Employee("Another Dev"),
                            new Employee("To Be Fired")
                    ));

    Employee newBoss = new Employee("Big Boss")
            .addSubordinates(
                    new Employee("Great Developer"),
                    new Employee("Team Lead").addSubordinates(
                            new Employee("Another Dev")
                    ));

    //when
    Diff diff = javers.compare(oldBoss, newBoss);

    //then
    assertThat(diff.getChangesByType(ObjectRemoved.class)).hasSize(1);

    System.out.println(diff);
  }
Diff:
* object removed: Employee/To Be Fired
* changes on Employee/Team Lead :
  - 'subordinates' collection changes :
    1. 'Employee/To Be Fired' removed  

shouldDetectSalaryChange():

  /** {@link ValueChange} example */
  @Test
  public void shouldDetectSalaryChange(){
    //given
    Javers javers = JaversBuilder.javers().build();

    Employee oldBoss = new Employee("Big Boss")
            .addSubordinates(
                    new Employee("Noisy Manager"),
                    new Employee("Great Developer", 10000));

    Employee newBoss = new Employee("Big Boss")
            .addSubordinates(
                    new Employee("Noisy Manager"),
                    new Employee("Great Developer", 20000));

    //when
    Diff diff = javers.compare(oldBoss, newBoss);

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

    assertThat(change.getAffectedLocalId()).isEqualTo("Great Developer");
    assertThat(change.getPropertyName()).isEqualTo("salary");
    assertThat(change.getLeft()).isEqualTo(10000);
    assertThat(change.getRight()).isEqualTo(20000);

    System.out.println(diff);
  }
Diff:
* changes on Employee/Great Developer :
  - 'salary' changed from '10000' to '20000'

shouldDetectBossChange():

  /** {@link ReferenceChange} example */
  @Test
  public void shouldDetectBossChange() {
    //given
    Javers javers = JaversBuilder.javers().build();

    Employee oldBoss = new Employee("Big Boss")
        .addSubordinates(
             new Employee("Manager One")
                 .addSubordinate(new Employee("Great Developer")),
             new Employee("Manager Second"));

    Employee newBoss = new Employee("Big Boss")
        .addSubordinates(
             new Employee("Manager One"),
             new Employee("Manager Second")
                 .addSubordinate(new Employee("Great Developer")));

    //when
    Diff diff = javers.compare(oldBoss, newBoss);

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

    assertThat(change.getAffectedLocalId()).isEqualTo("Great Developer");
    assertThat(change.getLeft().value()).endsWith("Manager One");
    assertThat(change.getRight().value()).endsWith("Manager Second");

    System.out.println(diff);
  }
Diff:
* changes on Employee/Manager One :
  - 'subordinates' collection changes :
    0. 'Employee/Great Developer' removed
* changes on Employee/Great Developer :
  - 'boss' changed from 'Employee/Manager One' to 'Employee/Manager Second'
* changes on Employee/Manager Second :
  - 'subordinates' collection changes :
    0. 'Employee/Great Developer' added  

Compare top-level Value Objects

This example shows how to find a diff between two objects of the Address class. Address is a typical Value Object, it doesn’t have its own identity. It’s just a complex value holder.

The case
We have two objects, address1 and address2. These objects represent two different addresses. To find out what the difference is, just call:

javers.compare(address1, address2)

What’s important
When JaVers knows nothing about a class, it treats it as Value Object. As we said in the previous example, JaVers compares only objects with the same GlobalId.

What’s the Address Id? It’s based on the path in the object graph. In this case, both objects are roots, so the path is simply '/' and the GlobalId is 'org.javers.core.examples.model.Address/'.

Address.java:

public class Address {
    private String city;

    private String street;

    ... // omitted
}

BasicValueObjectDiffExample.java:

  @Test
  public void shouldCompareTwoObjects() {

    //given
    Javers javers = JaversBuilder.javers().build();

    Address address1 = new Address("New York","5th Avenue");
    Address address2 = new Address("New York","6th Avenue");

    //when
    Diff diff = javers.compare(address1, address2);

    //then
    //there should be one change of type {@link ValueChange}
    ValueChange change = diff.getChangesByType(ValueChange.class).get(0);

    assertThat(diff.getChanges()).hasSize(1);
    assertThat(change.getAffectedGlobalId().value())
              .isEqualTo("org.javers.core.examples.model.Address/");
    assertThat(change.getPropertyName()).isEqualTo("street");
    assertThat(change.getLeft()).isEqualTo("5th Avenue");
    assertThat(change.getRight()).isEqualTo("6th Avenue");

    System.out.println(diff);
  }

The output of running this program is:

Diff:
* changes on org.javers.core.examples.model.Address/ :
  - 'street' changed from '5th Avenue' to '6th Avenue'

Compare top-level collections

JaVers can compare arbitrary complex structures of objects, including collections passed as top-level handles.

If you want to compare top-level collections with Primitives or Values (see domain-model-mapping), you can use the standard javers.compare(Object, Object) method. Collection items will be compared using equals(), resulting in a flat list of Changes.

But when you need to compare top-level collections with complex items, like Entities or Value Objects, use javers.compareCollections(Collection, Collection, Class). This method builds object graphs and compares them deeply, using itemClass as a hint about the item’s type.

The case
When collections are properties of a domain object, for example:

public class Boss {
    @Id private String name;

    private final List<Person> subordinates = new ArrayList<>();
}

JaVers uses Reflection and captures Person as the item type in the subordinates collection.

But when collections are passed as top-level references, for example:

Diff diff = javers.compare(oldList, newList);

due to type erasure, there is no way to statically determine the type of items stored in collections.

Luckily, compareCollections() comes to the rescue and gives you exactly the same diff result for top-level collections as if they were object properties.

ComparingTopLevelCollectionExample.java:

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

    List<Person> oldList = Lists.asList( new Person("tommy", "Tommy Smart") );
    List<Person> newList = Lists.asList( new Person("tommy", "Tommy C. Smart") );

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

    //then
    //there should be one change of type {@link ValueChange}
    ValueChange change = diff.getChangesByType(ValueChange.class).get(0);

    assertThat(diff.getChanges()).hasSize(1);
    assertThat(change.getPropertyName()).isEqualTo("name");
    assertThat(change.getLeft()).isEqualTo("Tommy Smart");
    assertThat(change.getRight()).isEqualTo("Tommy C. Smart");

    System.out.println(diff);
  }

The output of running this program is:

Diff:
* changes on org.javers.core.examples.model.Person/tommy :
  - 'name' changed from 'Tommy Smart' to 'Tommy C. Smart'

Groovy diff example

In JaVers we love the Groovy language. From the very beginning of the JaVers project, we used the Spock framework for writing tests. One of Groovy’s killer features is excellent interoperability with Java. Looking from the other side, modern Java frameworks should be Groovy friendly.

As you know, all Java classes extend the Object class. All Groovy classes extend GroovyObject, that’s how Groovy implements its metaprogramming features.

Good news, JaVers is fully compatible with Groovy! You can compare and commit Groovy objects in the same way as plain Java objects. Let’s see how it works:

GroovyDiffExample.groovy:

class GroovyDiffExample extends Specification {

    @TupleConstructor
    class Person {
        @Id login
        String lastName
    }

    def "should calculate diff for GroovyObjects"(){
      given:
      def javers = JaversBuilder.javers().build()

      when:
      def diff = javers.compare(
          new Person('bob','Uncle'),
          new Person('bob','Martin')
      )

      then:
      diff.changes.size() == 1
      diff.changes[0].left == 'Uncle'
      diff.changes[0].right == 'Martin'
    }
}

No special JaVers configuration is required for Groovy. In the example, we use the FIELD (default) mapping style. Since Groovy generates getters and setters on the fly, you can also use the BEAN mapping style without adding boilerplate code to domain classes.

Custom comparators example

In this example, we show how to use a CustomPropertyComparator.

Our comparator is called FunnyStringComparator, and it compares Strings as character sets. The logic is simple. When two Strings are built from the same set of characters — we don’t see a difference. Otherwise, we log each added or removed character.

This full example is in CustomPropertyComparatorExample.groovy coded as s Spock test. Here we show the most essential parts.

First, the comparator implementation:

class FunnyStringComparator implements CustomPropertyComparator<String, SetChange> {
    @Override
    Optional<SetChange> compare(String left, String right, GlobalId affectedId, Property property) {
        if (equals(left, right)) {
            return Optional.empty()
        }

        Set leftSet = left.toCharArray().toSet()
        Set rightSet = right.toCharArray().toSet()

        List<ContainerElementChange> changes = []
        Sets.difference(leftSet, rightSet).forEach{c -> changes.add(new ValueRemoved(c))}
        Sets.difference(rightSet, leftSet).forEach{c -> changes.add(new ValueAdded(c))}

        return Optional.of(new SetChange(affectedId, property.getName(), changes))
    }

    @Override
    boolean equals(String a, String b) {
        a.toCharArray().toSet() == b.toCharArray().toSet()
    }
}

In all tests, we compare two objects which have a String property and also a List<String> property:

class Entity {
    String value
    List<String> values
}

This distinction is important because JaVers compares list items using simple equals(a, b) method and property values using more sophisticated compare(...) method.

Ok, let’s see how FunnyStringComparator compares String properties:

def "should use FunnyStringComparator to compare String properties"(){
    given:
    def javers = JaversBuilder.javers()
            .registerCustomComparator(new FunnyStringComparator(), String).build()

    when:
    def diff = javers.compare(new Entity(value: "aaa"), new Entity(value: "a"))
    println "first diff: "+ diff

    then:
    diff.changes.size() == 0

    when:
    diff = javers.compare(new Entity(value: "aaa"), new Entity(value: "b"))
    println "second diff: "+ diff

    then:
    diff.changes.size() == 1
    diff.changes[0] instanceof SetChange
    diff.changes[0].changes.size() == 2 // two item changes in this SetChange
}

output:

first diff: Diff:

second diff: Diff:
* changes on org.javers.core.examples.CustomPropertyComparatorExample$Entity/ :
  - 'value' collection changes :
    . 'a' removed
    . 'b' added

FunnyStringComparator can also compare Strings when they are stored in a list:

def "should use FunnyStringComparator to compare Strings in lists"(){
    given:
    def javers = JaversBuilder.javers()
            .registerCustomComparator(new FunnyStringComparator(), String).build()

    when:
    def diff = javers.compare(new Entity(values: ["aaa"]), new Entity(values: ["a"]))
    println "first diff: "+ diff

    then:
    diff.changes.size() == 0

    when:
    diff = javers.compare(new Entity(values: ["aaa"]), new Entity(values: ["a", "bb"]))
    println "second diff: "+ diff

    then:
    diff.changes.size() == 1
    diff.changes[0] instanceof ListChange
    diff.changes[0].changes.size() == 1 // one item change in this ListChange
}

output:

first diff: Diff:

second diff: Diff:
* changes on org.javers.core.examples.CustomPropertyComparatorExample$Entity/ :
  - 'values' collection changes :
    1. 'bb' added