Reading the JSON documents as Maps and comparing them

You could read both JSON documents as Map<K, V>. See the below examples for Jackson and Gson:

ObjectMapper mapper = new ObjectMapper();
TypeReference<HashMap<String, Object>> type = 
    new TypeReference<HashMap<String, Object>>() {};

Map<String, Object> leftMap = mapper.readValue(leftJson, type);
Map<String, Object> rightMap = mapper.readValue(rightJson, type);
Gson gson = new Gson();
Type type = new TypeToken<Map<String, Object>>(){}.getType();

Map<String, Object> leftMap = gson.fromJson(leftJson, type);
Map<String, Object> rightMap = gson.fromJson(rightJson, type);

Then use Guava's Maps.difference(Map<K, V>, Map<K, V>) to compare them. It returns a MapDifference<K, V> instance:

MapDifference<String, Object> difference = Maps.difference(leftMap, rightMap);

If you are not happy with the result, you can consider flattening the maps and then compare them. It will provide better comparison results especially for nested objects and arrays.

Creating flat Maps for the comparison

To flat the map, you can use:

public final class FlatMapUtil {

    private FlatMapUtil() {
        throw new AssertionError("No instances for you!");
    }

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

    private static 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<>(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>(entry.getKey() + "/" + i, list.get(i)))
                    .flatMap(FlatMapUtil::flatten);
        }

        return Stream.of(entry);
    }
}

It uses the JSON Pointer notation defined in the RFC 6901 for the keys, so you can easily locate the values.

Example

Consider the following JSON documents:

{
  "name": {
    "first": "John",
    "last": "Doe"
  },
  "address": null,
  "birthday": "1980-01-01",
  "company": "Acme",
  "occupation": "Software engineer",
  "phones": [
    {
      "number": "000000000",
      "type": "home"
    },
    {
      "number": "999999999",
      "type": "mobile"
    }
  ]
}
{
  "name": {
    "first": "Jane",
    "last": "Doe",
    "nickname": "Jenny"
  },
  "birthday": "1990-01-01",
  "occupation": null,
  "phones": [
    {
      "number": "111111111",
      "type": "mobile"
    }
  ],
  "favorite": true,
  "groups": [
    "close-friends",
    "gym"
  ]
}

And the following code to compare them and show the differences:

Map<String, Object> leftFlatMap = FlatMapUtil.flatten(leftMap);
Map<String, Object> rightFlatMap = FlatMapUtil.flatten(rightMap);

MapDifference<String, Object> difference = Maps.difference(leftFlatMap, rightFlatMap);

System.out.println("Entries only on the left\n--------------------------");
difference.entriesOnlyOnLeft()
          .forEach((key, value) -> System.out.println(key + ": " + value));

System.out.println("\n\nEntries only on the right\n--------------------------");
difference.entriesOnlyOnRight()
          .forEach((key, value) -> System.out.println(key + ": " + value));

System.out.println("\n\nEntries differing\n--------------------------");
difference.entriesDiffering()
          .forEach((key, value) -> System.out.println(key + ": " + value));

It will produce the following output:

Entries only on the left
--------------------------
/address: null
/phones/1/number: 999999999
/phones/1/type: mobile
/company: Acme


Entries only on the right
--------------------------
/name/nickname: Jenny
/groups/0: close-friends
/groups/1: gym
/favorite: true


Entries differing
--------------------------
/birthday: (1980-01-01, 1990-01-01)
/occupation: (Software engineer, null)
/name/first: (John, Jane)
/phones/0/number: (000000000, 111111111)
/phones/0/type: (home, mobile)
Answer from cassiomolin on Stack Overflow
Top answer
1 of 4
102

Reading the JSON documents as Maps and comparing them

You could read both JSON documents as Map<K, V>. See the below examples for Jackson and Gson:

ObjectMapper mapper = new ObjectMapper();
TypeReference<HashMap<String, Object>> type = 
    new TypeReference<HashMap<String, Object>>() {};

Map<String, Object> leftMap = mapper.readValue(leftJson, type);
Map<String, Object> rightMap = mapper.readValue(rightJson, type);
Gson gson = new Gson();
Type type = new TypeToken<Map<String, Object>>(){}.getType();

Map<String, Object> leftMap = gson.fromJson(leftJson, type);
Map<String, Object> rightMap = gson.fromJson(rightJson, type);

Then use Guava's Maps.difference(Map<K, V>, Map<K, V>) to compare them. It returns a MapDifference<K, V> instance:

MapDifference<String, Object> difference = Maps.difference(leftMap, rightMap);

If you are not happy with the result, you can consider flattening the maps and then compare them. It will provide better comparison results especially for nested objects and arrays.

Creating flat Maps for the comparison

To flat the map, you can use:

public final class FlatMapUtil {

    private FlatMapUtil() {
        throw new AssertionError("No instances for you!");
    }

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

    private static 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<>(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>(entry.getKey() + "/" + i, list.get(i)))
                    .flatMap(FlatMapUtil::flatten);
        }

        return Stream.of(entry);
    }
}

It uses the JSON Pointer notation defined in the RFC 6901 for the keys, so you can easily locate the values.

Example

Consider the following JSON documents:

{
  "name": {
    "first": "John",
    "last": "Doe"
  },
  "address": null,
  "birthday": "1980-01-01",
  "company": "Acme",
  "occupation": "Software engineer",
  "phones": [
    {
      "number": "000000000",
      "type": "home"
    },
    {
      "number": "999999999",
      "type": "mobile"
    }
  ]
}
{
  "name": {
    "first": "Jane",
    "last": "Doe",
    "nickname": "Jenny"
  },
  "birthday": "1990-01-01",
  "occupation": null,
  "phones": [
    {
      "number": "111111111",
      "type": "mobile"
    }
  ],
  "favorite": true,
  "groups": [
    "close-friends",
    "gym"
  ]
}

And the following code to compare them and show the differences:

Map<String, Object> leftFlatMap = FlatMapUtil.flatten(leftMap);
Map<String, Object> rightFlatMap = FlatMapUtil.flatten(rightMap);

MapDifference<String, Object> difference = Maps.difference(leftFlatMap, rightFlatMap);

System.out.println("Entries only on the left\n--------------------------");
difference.entriesOnlyOnLeft()
          .forEach((key, value) -> System.out.println(key + ": " + value));

System.out.println("\n\nEntries only on the right\n--------------------------");
difference.entriesOnlyOnRight()
          .forEach((key, value) -> System.out.println(key + ": " + value));

System.out.println("\n\nEntries differing\n--------------------------");
difference.entriesDiffering()
          .forEach((key, value) -> System.out.println(key + ": " + value));

It will produce the following output:

Entries only on the left
--------------------------
/address: null
/phones/1/number: 999999999
/phones/1/type: mobile
/company: Acme


Entries only on the right
--------------------------
/name/nickname: Jenny
/groups/0: close-friends
/groups/1: gym
/favorite: true


Entries differing
--------------------------
/birthday: (1980-01-01, 1990-01-01)
/occupation: (Software engineer, null)
/name/first: (John, Jane)
/phones/0/number: (000000000, 111111111)
/phones/0/type: (home, mobile)
2 of 4
54

Creating a JSON Patch document

Alternatively to the approach described in the other answer, you could use the Java API for JSON Processing defined in the JSR 374 (it doesn't use on Gson or Jackson). The following dependencies are required:

<!-- Java API for JSON Processing (API) -->
<dependency>
    <groupId>javax.json</groupId>
    <artifactId>javax.json-api</artifactId>
    <version>1.1.2</version>
</dependency>

<!-- Java API for JSON Processing (implementation) -->
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.json</artifactId>
    <version>1.1.2</version>
</dependency>

Then you can create a JSON diff from the JSON documents. It will produce a JSON Patch document as defined in the RFC 6902:

JsonPatch diff = Json.createDiff(source, target);

When applied to the source document, the JSON Patch yields the target document. The JSON Patch can be applied to the source document using:

JsonObject patched = diff.apply(source);

Creating a JSON Merge Patch document

Depending on your needs, you could create a JSON Merge Patch document as defined in the RFC 7396:

JsonMergePatch mergeDiff = Json.createMergeDiff(source, target);

When applied to the source document, the JSON Merge Patch yields the target document. To patch the source, use:

JsonValue patched = mergeDiff.apply(source);

Pretty printing JSON documents

To pretty print the JSON documents, you can use:

System.out.println(format(diff.toJsonArray()));
System.out.println(format(mergeDiff.toJsonValue()));
public static String format(JsonValue json) {
    StringWriter stringWriter = new StringWriter();
    prettyPrint(json, stringWriter);
    return stringWriter.toString();
}

public static void prettyPrint(JsonValue json, Writer writer) {
    Map<String, Object> config =
            Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true);
    JsonWriterFactory writerFactory = Json.createWriterFactory(config);
    try (JsonWriter jsonWriter = writerFactory.createWriter(writer)) {
        jsonWriter.write(json);
    }
}

Example

Consider the following JSON documents:

{
  "name": {
    "first": "John",
    "last": "Doe"
  },
  "address": null,
  "birthday": "1980-01-01",
  "company": "Acme",
  "occupation": "Software engineer",
  "phones": [
    {
      "number": "000000000",
      "type": "home"
    },
    {
      "number": "999999999",
      "type": "mobile"
    }
  ]
}
{
  "name": {
    "first": "Jane",
    "last": "Doe",
    "nickname": "Jenny"
  },
  "birthday": "1990-01-01",
  "occupation": null,
  "phones": [
    {
      "number": "111111111",
      "type": "mobile"
    }
  ],
  "favorite": true,
  "groups": [
    "close-friends",
    "gym"
  ]
}

And the following code to produce a JSON Patch:

JsonValue source = Json.createReader(new StringReader(leftJson)).readValue();
JsonValue target = Json.createReader(new StringReader(rightJson)).readValue();

JsonPatch diff = Json.createDiff(source.asJsonObject(), target.asJsonObject());
System.out.println(format(diff.toJsonArray()));

It will produce the following output:

[
    {
        "op": "replace",
        "path": "/name/first",
        "value": "Jane"
    },
    {
        "op": "add",
        "path": "/name/nickname",
        "value": "Jenny"
    },
    {
        "op": "remove",
        "path": "/address"
    },
    {
        "op": "replace",
        "path": "/birthday",
        "value": "1990-01-01"
    },
    {
        "op": "remove",
        "path": "/company"
    },
    {
        "op": "replace",
        "path": "/occupation",
        "value": null
    },
    {
        "op": "replace",
        "path": "/phones/1/number",
        "value": "111111111"
    },
    {
        "op": "remove",
        "path": "/phones/0"
    },
    {
        "op": "add",
        "path": "/favorite",
        "value": true
    },
    {
        "op": "add",
        "path": "/groups",
        "value": [
            "close-friends",
            "gym"
        ]
    }
]

Now consider the following code to produce a JSON Merge Patch:

JsonValue source = Json.createReader(new StringReader(leftJson)).readValue();
JsonValue target = Json.createReader(new StringReader(rightJson)).readValue();

JsonMergePatch mergeDiff = Json.createMergeDiff(source, target);
System.out.println(format(mergeDiff.toJsonValue()));

It will produce the following output:

{
    "name": {
        "first": "Jane",
        "nickname": "Jenny"
    },
    "address": null,
    "birthday": "1990-01-01",
    "company": null,
    "occupation": null,
    "phones": [
        {
            "number": "111111111",
            "type": "mobile"
        }
    ],
    "favorite": true,
    "groups": [
        "close-friends",
        "gym"
    ]
}

Different results when applying the patches

When the patch document is applied, the results are slightly different for the approaches described above. Consider the following code that applies JSON Patch to a document:

JsonPatch diff = ...
JsonValue patched = diff.apply(source.asJsonObject());
System.out.println(format(patched));

It produces:

{
    "name": {
        "first": "Jane",
        "last": "Doe",
        "nickname": "Jenny"
    },
    "birthday": "1990-01-01",
    "occupation": null,
    "phones": [
        {
            "number": "111111111",
            "type": "mobile"
        }
    ],
    "favorite": true,
    "groups": [
        "close-friends",
        "gym"
    ]
}

Now consider the following code that applies JSON Merge Patch to a document:

JsonMergePatch mergeDiff = ...
JsonValue patched = mergeDiff.apply(source);
System.out.println(format(patched));

It produces:

{
    "name": {
        "first": "Jane",
        "last": "Doe",
        "nickname": "Jenny"
    },
    "birthday": "1990-01-01",
    "phones": [
        {
            "number": "111111111",
            "type": "mobile"
        }
    ],
    "favorite": true,
    "groups": [
        "close-friends",
        "gym"
    ]
}

In the first example, the occupation property is null. In the second example, it's omitted. It's due to the null semantics on JSON Merge Patch. From the RFC 7396:

If the target does contain the member, the value is replaced. Null values in the merge patch are given special meaning to indicate the removal of existing values in the target. [...]

This design means that merge patch documents are suitable for describing modifications to JSON documents that primarily use objects for their structure and do not make use of explicit null values. The merge patch format is not appropriate for all JSON syntaxes.

🌐
GitHub
github.com › deblockt › json-diff
GitHub - deblockt/json-diff: A library to generate a json diff on java · GitHub
final var expectedJson = "{\"name\": \"John\", \"age\": 30, \"city\": \"Paris\"}"; final var receivedJson = "{\"name\": \"Jane\", \"age\": 30, \"country\": \"France\"}"; // Define your matcher final var jsonMatcher = new CompositeJsonMatcher( new LenientJsonArrayPartialMatcher(), new LenientJsonObjectPartialMatcher(), new StrictPrimitivePartialMatcher() ); // Generate the diff final var diff = DiffGenerator.diff(expectedJson, receivedJson, jsonMatcher); // Display errors System.out.println(OnlyErrorDiffViewer.from(diff)); // Get similarity score (0-100) System.out.println("Similarity: " + diff.similarityRate() + "%");
Starred by 54 users
Forked by 10 users
Languages   Java
🌐
JSON Diff
jsondiff.com
JSON Diff - The semantic JSON compare tool
Validate, format, and compare two JSON documents. See the differences between the objects instead of just the new lines and mixed up properties.
🌐
Baeldung
baeldung.com › home › json › jackson › compare two json objects with jackson
Compare Two JSON Objects with Jackson | Baeldung
January 8, 2024 - JsonNode.equals works quite well in most cases. Jackson also provides JsonNode.equals(comparator, JsonNode) to configure a custom Java Comparator object.
🌐
GitHub
github.com › fslev › json-compare
GitHub - fslev/json-compare: A Java library for comparing JSONs · GitHub
Compare any JSON convertible Java objects and check the differences between them when matching fails.
Starred by 75 users
Forked by 14 users
Languages   Java
🌐
DEV Community
dev.to › keploy › diff-json-a-complete-guide-to-comparing-json-data-3e31
Diff JSON – A Complete Guide to Comparing JSON Data - DEV Community
October 15, 2024 - The Jackson library in Java provides powerful tools for JSON comparison and manipulation. JSON Diff Best Practices To ensure reliable JSON comparison, follow these best practices: • Ignore Order When Possible: If order doesn’t matter, avoid strict array comparison to prevent unnecessary mismatches.
🌐
Stanislav Myachenkov
smyachenkov.com › posts › how to compare json documents in java
How To Compare JSON Documents In Java | Stanislav Myachenkov
June 9, 2020 - List<Difference> compare(Object from, Object to, String path) The value of the key can be primitive value, array, or an object, so we need to handle all those situations. Let’s create functions to check if both object belong to the same category. Set<Class<?>> JSON_PRIMITIVES = Set.of( Integer.class, Long.class, Double.class, String.class ); boolean oneIsPrimitive(Class<?> from, Class<?> to) { return JSON_PRIMITIVES.contains(to) || JSON_PRIMITIVES.contains(from); } boolean bothAreObjects(Object from, Object to) { return from instanceof Map && to instanceof Map; } boolean bothAreArrays(Class<?> from, Class<?> to) { return from == ArrayList.class && to == ArrayList.class; }
Find elsewhere
🌐
Cassiomolin
cassiomolin.com › programming › comparing-json-documents-in-java-with-jsonp
Comparing JSON documents in Java with JSON‑P | Cássio Mazzochi Molin
August 8, 2019 - And this document with the differences, when applied to the source document, yields the target document. To create JSON Patch document with the differences between a source and target documents, we can use the createDiff() method:
🌐
Baeldung
baeldung.com › home › json › compare two json objects with gson
Compare Two JSON Objects with Gson | Baeldung
January 8, 2024 - Comparison of JSON in its string representation is often less effective than comparing it semantically. Here we use Gson to load the JSON and compare the data.
🌐
Medium
medium.com › threadsafe › how-to-compare-two-json-structures-in-java-when-the-order-of-fields-keeps-changing-f844df37e45a
How to compare two JSON objects in Java tests and when the order of values is not important | ThreadSafe
June 17, 2025 - The problem occurs when the produced JSON structure by the API is the same as expected, but the order of the entities inside of the result is not the same (Similar to the image below).
🌐
GitHub
gist.github.com › 95c58862f54cee57ae68e58bee2378f2
Compare two JSON Objects and get Difference. · GitHub
Compare two JSON Objects and get Difference. Raw · JsonUtils.java · This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
🌐
Cassiomolin
cassiomolin.com › programming › comparing-json-documents-in-java
Comparing JSON documents in Java — Cássio Mazzochi Molin
July 23, 2018 - MapDifference<String, Object> difference = Maps.difference(left, right); Everything was good until I had to compare complex JSON documents, with nested objects and arrays.
🌐
Coderanch
coderanch.com › t › 533531 › java › compare-json-object-java-side
how to compare two json object at java side (Beginning Java forum at Coderanch)
How can i write json object comparision? And, Please provide sample code base Thanks in advance ... I recommend using this Java library for comparing JSONs: https://github.com/fslev/json-compare It has all kinds of features.
🌐
DEV Community
dev.to › keploy › json-diff-comparing-and-identifying-changes-in-json-data-2m3m
JSON Diff: Comparing and Identifying Changes in JSON Data - DEV Community
January 14, 2025 - JSON diff refers to the process of identifying and analyzing the differences between two JSON files, objects, or structures. It involves comparing key-value pairs, nested arrays, and objects to detect mismatches, changes, or missing elements.
🌐
Medium
medium.com › @mujtabauddinfurqan › what-i-learned-while-comparing-two-jsons-1150ae902b2c
What I learned while comparing two JSONs | by Mujtabauddin Furqan | Medium
February 23, 2019 - Given a patch, it apply it to source JSON and return a target JSON which can be ( JSON object or array or value ). This operation performed on a clone of source JSON ( thus, the source JSON is unmodified and can be used further).
🌐
GitHub
github.com › wI2L › jsondiff
GitHub - wI2L/jsondiff: Compute the diff between two JSON documents as a series of JSON Patch (RFC6902) operations · GitHub
The LCS() option instruct the diff generator to compute the Longest common subsequence of the source and target arrays, and use it to generate a list of operations that is more succinct and more faithfully represents the differences. ... This option is experimental and might be revised in the future. The Ignores() option allows to exclude one or more JSON fields/values from the generated diff.
Starred by 622 users
Forked by 42 users
Languages   Go 99.4% | Shell 0.6%