118

Is there a Java utility library that is analogous to the Unix program diff, but for Objects? I'm looking for something that can compare two objects of the same type and generate a data structure that represents the differences between them (and can recursively compare differences in instance variables). I'm not looking for a Java implementation of a text diff. I'm also not looking for help with how to use reflection to do this.

The application I'm maintaining has a fragile implementation of this functionality that had some poor design choices and that needs to be rewritten, but it would be even better if we could use something off the shelf.

Here's an example of the kind of thing I'm looking for:

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();

a.setProp1("A");
a.setProp2("X");

b.setProp1("B");
b.setProp2("X");

DiffDataStructure diff = OffTheShelfUtility.diff(a, b);  // magical recursive comparison happens here

After comparison, the utility would tell me that "prop1" is different between the two objects and "prop2" is the same. I think it's most natural for DiffDataStructure to be a tree, but I'm not going to be picky if the code is reliable.

Kaypro II
  • 3,210
  • 8
  • 30
  • 41
  • 9
    check this, http://code.google.com/p/jettison/ – denolk Nov 03 '11 at 20:27
  • 1
    possible duplicate of [How to test for equality of complex object graphs?](http://stackoverflow.com/questions/1411612/how-to-test-for-equality-of-complex-object-graphs) – skaffman Nov 03 '11 at 22:52
  • 18
    This isn't a duplicate of "How to test for equality of complex object graphs?" I'm looking for a library, not advice on how to do it. Furthermore, I'm interested in the delta, not if they're equal or not. – Kaypro II Nov 04 '11 at 17:58
  • 1
    Look at https://github.com/jdereg/java-util which has a GraphComparator utilities. This class will generate a List of delta's between to object graphs. In addition, it has can merge (apply) a delta to a graph. Effectively it is List = GraphComparator(rootA, rootB). As well as GraphComparator.applyDelta(rootB, List). – John DeRegnaucourt Jul 23 '16 at 19:59
  • I know you don't want to use reflection, but that's definitely the easiest/simple way to implement something like this – RobOhRob May 21 '19 at 17:44
  • @denolk Is there a new repository for jettison? – koppor Jul 16 '20 at 14:49

8 Answers8

55

Might be a little late, but I was in the same situation like you and ended up creating my own library for exactly your use-case. Since I was forced to come up with a solution myself, I decided to release it on Github, to spare others the hard work. You can find it here: https://github.com/SQiShER/java-object-diff

--- Edit ---

Here is a little usage example based on the OPs code:

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();

a.setProp1("A");
a.setProp2("X");

b.setProp1("B");
b.setProp2("X");

DiffNode diff = ObjectDifferBuilder.buildDefault().compare(a, b);

assert diff.hasChanges();
assert diff.childCount() == 1;
assert diff.getChild('prop1').getState() == DiffNode.State.CHANGED;
SQiShER
  • 1,045
  • 7
  • 10
  • 1
    May I point you to this post showing a use case of java-object-diff? http://stackoverflow.com/questions/12104969/java-mongodb-object-versioning Thanks a lot for this helpful library! – Matthias Wuttke Aug 31 '12 at 14:34
  • I had a look at your post an wrote an answer that turned out to be way too long to post as a comment, so here is the "gist" of it: https://gist.github.com/3555555 – SQiShER Aug 31 '12 at 16:40
  • 2
    Neat. I ended up writing my own implementation too. I'm not sure I'll get a chance to really explore your implementation, but I'm curious about how you handle Lists. I ended up handling all collection differences as differences of Maps (by defining the keys in different ways depending on if it's a list, set, or map; then using set operations on the keySet). However, that method is over-sensitive to list-index changes. I didn't solve that problem because I didn't need to for my application, but I'm curious about how you handled it (more like a text-diff, I'd assume). – Kaypro II Sep 04 '12 at 22:51
  • 2
    You bring up a good point. Collections are really tough to deal with. Especially if you support merging, like I do. I solved the problem by using object identities to detect whether an item has been added, changed or removed (via hashCode, equals and contains). The good thing about that is, that I can treat all collections the same. The bad thing is, that I can't properly handle (for example) ArrayLists that contain the same object multiple times. I'd love to add support for that, but it seems to be pretty complicated and fortunately nobody asked for it, yet. :-) – SQiShER Sep 05 '12 at 20:54
  • Daniel, thanks for your comment! You are right, reconstructing the original object from the diffs is difficult, but this is in my case not too important. Originally, I stored previous versions of my object in the database - as you recommend. The problem was that most parts of the large documents did not change, there was a lot of duplicated data. This is why I started searching for alternatives and I found java-object-diff. I also experienced difficulties with collections/maps and altered my "equals" methods to check for equality of the database primary (and not the whole object), this helped. – Matthias Wuttke Sep 10 '12 at 06:37
  • Daniel, I basically ended up adding an override setting to treat lists as sets (and do the same sorts of things you did), since in my application we don't actually have proper lists, just sets that someone decided to put in a list. This looks interesting for a proper list impl: http://en.wikipedia.org/w/index.php?title=Diff&oldid=563831324#Algorithm. – Kaypro II Aug 02 '13 at 06:10
  • I tried this out and found the default diff shows spurious differences. Two identical objects, constructed separately, show up as being different when their properties exactly match. When the properties don't match, no additional differences are found. Coding to make it behave differently is not obvious, not documented, and worst of all, the test cases are not in Java. – Kieveli Sep 01 '16 at 18:03
  • Can you have a look at https://stackoverflow.com/questions/45586006/object-differ-haschanges-where-no-changes-should-be-detected @SQiShER ? It seems like the comparison doesn't work right there. All the classes can be found in a github repo in the comments – baao Aug 09 '17 at 15:39
  • @Michael I'd like to, but I can't find the Github link in the other question. :-) – SQiShER Aug 10 '17 at 19:36
  • The only problem this library does not print out fields & values which are not equal, just: "__DiffNode(state=CHANGED, type=com.test.testthis.dataobjects.User, 3 children, accessed via root element)__", but no information in toString() that difference in e.g. "__email, password & role__" fields – Ivan Gerasimenko Mar 12 '19 at 12:12
  • And there is no way to get children of DiffNode, so this lib is only useful in debugging mode :( – Ivan Gerasimenko Mar 12 '19 at 13:16
50

http://javers.org is library that does exacly what you need: has methods like compare(Object leftGraph, Object rightGraph) returning the Diff object. Diff contains a list of changes (ReferenceChange, ValueChange, PropertyChange) e.g.

given:
DummyUser user =  dummyUser("id").withSex(FEMALE).build();
DummyUser user2 = dummyUser("id").withSex(MALE).build();
Javers javers = JaversTestBuilder.newInstance()

when:
Diff diff = javers.compare(user, user2)

then:
diff.changes.size() == 1
ValueChange change = diff.changes[0]
change.leftValue == FEMALE
change.rightValue == MALE

It can handle cycles in graphs.

In addition you can get Snapshot of any graph object. Javers has JSON serializers and deserializers to snapshot, and changes so you can easily save them in database. With this library you can easily implement a module for auditing.

rekaszeru
  • 19,130
  • 7
  • 59
  • 73
Paweł Szymczyk
  • 1,638
  • 15
  • 20
  • 1
    this doesn't work. how do you create a Javers instance? – Ryan Vettese Jun 18 '14 at 18:16
  • I cant create an Javers instance, too. Please give us some hints. – marcel Jul 01 '14 at 13:27
  • 2
    Javers has excellent documentation that explains everything: [javers.org](http://javers.org/documentation/getting-started/#create-javers-instance) – Paweł Szymczyk Oct 31 '14 at 19:23
  • 2
    I tryed to use it but is only for Java >=7. Guys, there are still people working on projects on Java 6!!! – jesantana Jan 15 '15 at 16:08
  • 2
    What if the two objects that I compare has a list of objects internally and will I be seeing those differences too? What is the level of hierarchy that javers could compare? – Deepak Jul 20 '16 at 00:13
  • 1
    Yes you will see differences on internal objects. At the moment there is no threshold for hierarchy level. Javers will go as deep as it is possible, the limitation is stack size. – Paweł Szymczyk Sep 01 '16 at 11:20
  • Have used Javers for a while on production application... ended up with analysing heap dumps. Got a giant memory leak in the compare method. Was using SpringBoot 1.4.2 with org.javers:javers-core:3.0.0 – thorinkor Dec 15 '17 at 08:07
  • @thorinkor it's open source library so if you have some problems just submit pull request or report issue on Github. I think that all other users don't want to analyse the same things as you again. – Paweł Szymczyk Dec 15 '17 at 15:40
  • 1
    @Paweł Szymczyk the main problem was huge memory leak when using `Javers.compare` method. Our service was comparing related objects and this led to memory problems (after few hours of usage we were running out of heap memory + 100% CPU usage on all cores). Analysis of heap dumps have shown that `compare` method allocates about 90% of memory available for application. – thorinkor Jan 04 '18 at 09:11
  • @thorinkor was the memory leak fixed? – lapots Jan 03 '19 at 19:14
  • @lapots cannot say - I gave up on using this library, as the problems were critical for our project and client. We have ended up with implementation of own, simple objects comparator. – thorinkor Jan 04 '19 at 11:50
  • Javers has limitations over Sets collections. – ScanQR Jul 28 '20 at 09:24
18

You could also take a look at the solution from Apache. Most projects already have it on their classpath since its part of commons-lang.

Check difference for specific field(s):
http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/builder/DiffBuilder.html

Check difference by using reflection:
http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/builder/ReflectionDiffBuilder.html

LostMage
  • 451
  • 4
  • 11
  • I get an error when using these... `java.lang.NullPointerException: Cannot invoke "org.apache.commons.lang3.JavaVersion.atLeast(org.apache.commons.lang3.JavaVersion)" because "org.apache.commons.lang3.SystemUtils.JAVA_SPECIFICATION_VERSION_AS_ENUM" is null` – Jose Martinez Oct 08 '21 at 12:31
3

All the Javers library has support to only Java 7, I was in a situation since I want this to be used for a Java 6 project so I happened to take the source and change in a way it works for Java 6 below is the github code.

https://github.com/sand3sh/javers-forJava6

Jar link: https://github.com/sand3sh/javers-forJava6/blob/master/build/javers-forjava6.jar

I have only changed the Java 7 supported '<>' inherent cast conversions to Java 6 support I dont gurantee all the functionalities will work since I have commented few unnecessary code for me it works for all custom objects comparision.

Sandesh
  • 31
  • 3
2

Maybe this will help, depending on where you use this code, it could be useful or problematic. Tested this code.

    /**
 * @param firstInstance
 * @param secondInstance
 */
protected static void findMatchingValues(SomeClass firstInstance,
        SomeClass secondInstance) {
    try {
        Class firstClass = firstInstance.getClass();
        Method[] firstClassMethodsArr = firstClass.getMethods();

        Class secondClass = firstInstance.getClass();
        Method[] secondClassMethodsArr = secondClass.getMethods();


        for (int i = 0; i < firstClassMethodsArr.length; i++) {
            Method firstClassMethod = firstClassMethodsArr[i];
            // target getter methods.
            if(firstClassMethod.getName().startsWith("get") 
                    && ((firstClassMethod.getParameterTypes()).length == 0)
                    && (!(firstClassMethod.getName().equals("getClass")))
            ){

                Object firstValue;
                    firstValue = firstClassMethod.invoke(firstInstance, null);

                logger.info(" Value "+firstValue+" Method "+firstClassMethod.getName());

                for (int j = 0; j < secondClassMethodsArr.length; j++) {
                    Method secondClassMethod = secondClassMethodsArr[j];
                    if(secondClassMethod.getName().equals(firstClassMethod.getName())){
                        Object secondValue = secondClassMethod.invoke(secondInstance, null);
                        if(firstValue.equals(secondValue)){
                            logger.info(" Values do match! ");
                        }
                    }
                }
            }
        }
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
}
r0ast3d
  • 2,639
  • 1
  • 14
  • 18
  • 2
    Thanks, but we already have code that walks the object graph using reflection lists the changes. This questions isn't about how to do it, it's about trying to avoid re-inventing the wheel. – Kaypro II Nov 03 '11 at 22:36
  • Reinventing the wheel isn't bad if the reinvention already happened. (IE: The reinvented wheel was already paid for.) – Thomas Eding Nov 03 '11 at 22:43
  • 1
    I agree with you, but in this case the reinvented code is an unmaintainable mess. – Kaypro II Nov 04 '11 at 17:54
  • 3
    I'd be checking `Fields` not methods. Methods could be anything, like `getRandomNumber()`. – Bohemian Nov 04 '11 at 20:18
0

A good way to compare deeply all properties of an objects is to convert them to java.util.Map. Done that, the java.util.Map#equals will deeply compare the objects and the job is done !

The only problem is to convert the object to a Map. One way to do it is to use reflexion with org.codehaus.jackson.map.ObjectMapper.

Therefore, it exist a tool from com.google.common.collect.MapDifference which describe the defferences between the two maps.

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();

a.setProp1("A");
a.setProp2("X");

b.setProp1("B");
b.setProp2("X");

// Convert object to Map
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> aMap =  objectMapper.convertValue(a, Map.class);
Map<String, Object> bMap =  objectMapper.convertValue(b, Map.class);

aMap.equals(bMap); // --> false

// Show deeply all differences
MapDifference<String, Object> diff = Maps.difference(aMap, bMap);
-1

Firstly we have to convert our objects to map:

    public Map<String, Object> objectToMap(Object object) throws JsonProcessingException {
    var mapper = new ObjectMapper();
    final var type = new TypeReference<HashMap<String, Object>>() {

    };
    ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
    return mapper.readValue(ow.writeValueAsString(object), type);
}

after that convert the map to flatten map:

    public Map<String, Object> flatten(Map<String, Object> map) {
    return map.entrySet().stream()
        .flatMap(this::flatten)
        .collect(LinkedHashMap::new, (m, e) -> m.put(camelToUnderScore("/" + e.getKey()), e.getValue()),
            LinkedHashMap::putAll);
}

public Stream<Map.Entry<String, Object>> flatten(Map.Entry<String, Object> entry) {

    if (entry == null) {
        return Stream.empty();
    }

    if (entry.getValue() instanceof Map<?, ?>) {
        return ((Map<?, ?>) entry.getValue()).entrySet().stream()
            .flatMap(e -> flatten(
                new AbstractMap.SimpleEntry<>(camelToUnderScore(entry.getKey() + "/" + e.getKey()),
                    e.getValue())));
    }

    if (entry.getValue() instanceof List<?>) {
        List<?> list = (List<?>) entry.getValue();
        return IntStream.range(0, list.size())
            .mapToObj(i -> new AbstractMap.SimpleEntry<String, Object>(
                camelToUnderScore(entry.getKey() + "/" + i), list.get(i)))
            .flatMap(this::flatten);
    }

    return Stream.of(entry);
}

and finaly call getDifferenceBetween2Maps to get the differences:

    public Map<String, Object> getDifferenceBetween2Maps(final Map<String, Object> leftFlatMap,
                                                     final Map<String, Object> rightFlatMap) {
  
    final MapDifference<String, Object> difference = Maps.difference(leftFlatMap, rightFlatMap);

    var differencesList = new HashMap<String, Object>();
  
    differencesList.putAll(difference.entriesOnlyOnLeft());

    differencesList.putAll(difference.entriesOnlyOnRight());

    return differencesList;
}

using example :

Map<String, Object> oldObjectFlatMap = flatten(objectToMap(oldObject));
Map<String, Object> newObjectFlatMap = flatten(objectToMap(newObject));
var differencesList = getDifferenceBetween2Maps(oldObjectFlatMap , newObjectFlatMap);
-3

A simpler approach to quickly tell you if two objects are different would be is to use the apache commons library

    BeanComparator lastNameComparator = new BeanComparator("lname");
    logger.info(" Match "+bc.compare(firstInstance, secondInstance));
r0ast3d
  • 2,639
  • 1
  • 14
  • 18
  • 1
    That doesn't work for me because I need to know what changed, not just that there was a change somewhere. – Kaypro II Nov 03 '11 at 22:34