'Is there a Java library that can "diff" two Objects?

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.



Solution 1:[1]

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;

Solution 2:[2]

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.

Solution 3:[3]

Yes, the java-util library has a GraphComparator class that will compare two Java Object Graphs. It returns the difference as a List of deltas. The GraphComparator also permits you to merge (apply) the deltas as well. This code only has dependencies on the JDK, no other libraries.

Solution 4:[4]

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

Solution 5:[5]

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.

Solution 6:[6]

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

Solution 7:[7]

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) {
    // recuperation des differences entre les 2 maps
    final MapDifference<String, Object> difference = Maps.difference(leftFlatMap, rightFlatMap);

    var differencesList = new HashMap<String, Object>();
    // recuperation des donnees qui existe sur oldProjet et n existe pas sur newProjet
    differencesList.putAll(difference.entriesOnlyOnLeft());

    // recuperation des donnees qui existe sur newProjet et n existe pas sur oldProjet
    differencesList.putAll(difference.entriesOnlyOnRight());

    // recuperation des differences entre oldProjet et newProjet
    differencesList.putAll(difference.entriesDiffering());

    return differencesList;
}

using example :

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 rekaszeru
Solution 3 John DeRegnaucourt
Solution 4 LostMage
Solution 5 Sandesh
Solution 6 r0ast3d
Solution 7 Mohammed El Amine Mahmoudi