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:example -Dtest.single=BasicEntityDiffExample
./gradlew javers-core:example -Dtest.single=BasicValueObjectDiffExample
...

Compare two Entity objects

Let’s start from something simple. This example shows how to find a diff between two objects of Person class. Since every person has his own identity, Person class is an Entity (see domain-model-mapping for Entity definition).

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

javers.compare(tommyOld, tommyNew)

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

What’s important
Notice that both objects have the same Id value ('tommy'). That’s why they are matched and compared. JaVers compares only objects with the same GlobalId. In this case, it’s 'org.javers.core.examples.model.Person/tommy'.

Person.class:

package org.javers.core.examples.model;

import javax.persistence.Id;

public class Person {
    @Id
    private String login;
    private String name;

    public Person(String login, String name) {
        this.login = login;
        this.name = name;
    }

    public String getLogin() { return login; }

    public String getName() { return name; }
}

BasicEntityDiffExample.class:

package org.javers.core.examples;

import org.javers.core.Javers;
import org.javers.core.JaversBuilder;
import org.javers.core.diff.Diff;
import org.javers.core.diff.changetype.ValueChange;
import org.javers.core.examples.model.Person;
import org.junit.Test;
import static org.fest.assertions.api.Assertions.assertThat;

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

    Person tommyOld = new Person("tommy", "Tommy Smart");
    Person tommyNew = new Person("tommy", "Tommy C. Smart");

    //when
    Diff diff = javers.compare(tommyOld, tommyNew);

    //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.getAffectedGlobalId()
        .value()).isEqualTo("org.javers.core.examples.model.Person/tommy");
    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:
1. ValueChange{globalId:'org.javers.core.examples.model.Person/tommy',
               property:'name', oldVal:'Tommy Smart', newVal:'Tommy C. Smart'}

Compare ValueObjects

This example shows how to find a diff between two objects of Address class. Address is a typical ValueObject; it doesn’t have its own identity. It’s just a complex value holder (see domain-model-mapping for ValueObject definition).

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)

Configuration
In this case, no configuration is required since JaVers is going to map Address class as ValueObject by default.

What’s important
When JaVers knows nothing about a class, it treats it as ValueObject. As we said in the previous example, JaVers compares only objects with the same GlobalId. What’s the Address Id? Well, it’s a tricky beast…

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.class:

package org.javers.core.examples.model;

public class Address {
    private final String city;
    private final String street;

    public Address(String city, String street) {
        this.city = city;
        this.street = street;
    }

    public String getCity() { return city; }

    public String getStreet() { return street; }
}

BasicValueObjectDiffExample.class:

package org.javers.core.examples;

import org.javers.core.Javers;
import org.javers.core.JaversBuilder;
import org.javers.core.diff.Diff;
import org.javers.core.diff.changetype.ValueChange;
import org.javers.core.examples.model.Address;
import org.junit.Test;
import static org.fest.assertions.api.Assertions.assertThat;

public class BasicValueObjectDiffExample {

  @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:
1. ValueChange{globalId:'org.javers.core.examples.model.Address/',
               property:'street', oldVal:'5th Avenue', newVal:'6th Avenue'}

Compare graphs

JaVers can compare arbitrary complex structures of objects. In this example, we show how easily you can compare employee hierarchies.

For the simplicity of this example, the data model is reduced to one class, Employee (see below).

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 an employee hierarchy. We have two Employee objects, oldBoss and newBoss. These guys are roots and handles to our hierarchies.

We could consider the following types of changes:

We show code examples for three cases: employee hired, salary change and boss change (other cases are done similarly). See the tests in EmployeeHierarchiesDiffExample.class below.

Configuration
JaVers needs to know that Employee class is an Entity. It’s enough to annotate the name field with @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.

Employee.class:

package org.javers.core.examples.model;

import javax.persistence.Id;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Employee {

    @Id
    private final String name;

    private final int salary;

    private Employee boss;

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

    public Employee(String name) {
        this(name, 10000);
    }

    public Employee(String name, int salary) {
        checkNotNull(name);
        this.name = name;
        this.salary = salary;
    }

    public Employee addSubordinate(Employee employee) {
        checkNotNull(employee);
        employee.boss = this;
        subordinates.add(employee);
        return this;
    }

    // ...
}

EmployeeHierarchiesDiffExample.class:

package org.javers.core.examples;

import org.javers.core.Javers;
import org.javers.core.JaversBuilder;
import org.javers.core.diff.Diff;
import org.javers.core.diff.changetype.*;
import org.javers.core.examples.model.Employee;
import org.junit.Test;
import static org.fest.assertions.api.Assertions.assertThat;

public class EmployeeHierarchiesDiffExample {

  /** {@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);
  }

  /** {@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);
  }

  /** {@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);
  }

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

    Employee oldBoss = new Employee("Big Boss");
    Employee boss = oldBoss;
    for (int i=0; i<1000; i++){
        boss.addSubordinate(new Employee("Emp no."+i));
        boss = boss.getSubordinates().get(0);
    }

    Employee newBoss = new Employee("Big Boss");

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

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

The output of running this program is:

//.. shouldDetectSalaryChange()

1. ValueChange{
   globalId:'org.javers.core.examples.model.Employee/Great Developer',
   property:'salary', oldVal:'10000', newVal:'20000'}


//.. shouldDetectHired()

1. NewObject {
   globalId:'org.javers.core.examples.model.Employee/Hired Second'}
2. NewObject {
   globalId:'org.javers.core.examples.model.Employee/Hired One'}
3. ListChange{
   globalId:'org.javers.core.examples.model.Employee/Big Boss',
   property:'subordinates',
   containerChanges:[(1).added:'org.javers.core.examples.model.Employee/Hired One',
                     (2).added:'org.javers.core.examples.model.Employee/Hired Second']}


//.. shouldDetectBossChange()

Diff:
1. ReferenceChange{
   globalId:'org.javers.core.examples.model.Employee/Great Developer',
   property:'boss',
   oldRef:'org.javers.core.examples.model.Employee/Manager One',
   newRef:'org.javers.core.examples.model.Employee/Manager Second'}
2. ListChange{
   globalId:'org.javers.core.examples.model.Employee/Manager Second',
   property:'subordinates',
   containerChanges:[(0).added:'org.javers.core.examples.model.Employee/Great Developer']}
3. ListChange{
   globalId:'org.javers.core.examples.model.Employee/Manager One',
   property:'subordinates',
   containerChanges:[(0).removed:'org.javers.core.examples.model.Employee/Great Developer']}

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 simple items like 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 ValueObjects, use javers.compareCollections(Collection, Collection, Class). This method builds object graphs and compares them deeply, using itemClass as a hint about the items 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.class

package org.javers.core.examples;

import org.javers.common.collections.Lists;
import org.javers.core.Javers;
import org.javers.core.JaversBuilder;
import org.javers.core.diff.Diff;
import org.javers.core.diff.changetype.ValueChange;
import org.javers.core.examples.model.Person;
import org.junit.Test;
import java.util.List;
import static org.fest.assertions.api.Assertions.assertThat;

/**
 * @author bartosz.walacik
 */
public class ComparingTopLevelCollectionExample {

  @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);
  }
}

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. Recently, Groovy has started gaining momentum as a full-blown application language. 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 extends the Object class. All Groovy classes extends 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

package org.javers.core.examples

import groovy.transform.TupleConstructor
import org.javers.core.JaversBuilder
import org.javers.core.metamodel.annotation.Id
import spock.lang.Specification

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.