If you care for performance, you should not combine a linear search with another linear search; with the resulting time complexity can’t be fixed with parallel processing when the lists get large.

You should built a data structure which allows efficient lookups first:

Map<List<?>,Map<String, Object>> inputKeys = inputRecords.stream()
    .collect(Collectors.toMap(
        m -> Arrays.asList(m.get("ID"),m.get("Email")),
        m -> m,
        (a,b) -> { throw new IllegalStateException("duplicate "+a+" and "+b); },
        LinkedHashMap::new));

List<Map<String,Object>> matchinRecords = dbRecords.stream()
    .filter(m -> inputKeys.containsKey(Arrays.asList(m.get("ID"),m.get("Email"))))
    .collect(Collectors.toList());

matchinRecords.forEach(m -> inputKeys.remove(Arrays.asList(m.get("ID"),m.get("Email"))));
List<Map<String,Object>> notMatchinRecords = new ArrayList<>(inputKeys.values());

This solution will keep the identity of the Maps.

If you are only interested in the values associated with the "Email" key, it would be much simpler:

Map<Object,Object> notMatchinRecords = inputRecords.stream()
    .collect(Collectors.toMap(
        m -> m.get("ID"),
        m -> m.get("Email"),
        (a,b) -> { throw new IllegalStateException("duplicate"); },
        LinkedHashMap::new));

Object notPresent = new Object();
Map<Object,Object> matchinRecords = dbRecords.stream()
    .filter(m -> notMatchinRecords.getOrDefault(m.get("ID"), notPresent)
                                  .equals(m.get("Email")))
    .collect(Collectors.toMap(
        m -> m.get("ID"),
        m -> m.get("Email"),
        (a,b) -> { throw new IllegalStateException("duplicate"); },
        LinkedHashMap::new));

notMatchinRecords.keySet().removeAll(matchinRecords.keySet());

System.out.println("Matching Records: " + matchinRecords.size());
matchinRecords.forEach((id,email) -> System.out.println(email));

System.out.println("Non Matching Records" + notMatchinRecords.size());
notMatchinRecords.forEach((id,email) -> System.out.println(email));

The first variant can get extended to support more/other map entries easily:

List<String> keys = Arrays.asList("ID", "Email");

Function<Map<String,Object>,List<?>> getKey
    = m -> keys.stream().map(m::get).collect(Collectors.toList());

Map<List<?>,Map<String, Object>> inputKeys = inputRecords.stream()
    .collect(Collectors.toMap(
        getKey,
        m -> m,
        (a,b) -> { throw new IllegalStateException("duplicate "+a+" and "+b); },
        LinkedHashMap::new));

List<Map<String,Object>> matchinRecords = dbRecords.stream()
    .filter(m -> inputKeys.containsKey(getKey.apply(m)))
    .collect(Collectors.toList());

matchinRecords.forEach(m -> inputKeys.remove(getKey.apply(m)));
List<Map<String,Object>> notMatchinRecords = new ArrayList<>(inputKeys.values());
Answer from Holger on Stack Overflow
🌐
How to do in Java
howtodoinjava.com › home › collections framework › java hashmap › compare two maps in java: equals(), keys, values and diff
Compare Two Maps in Java: equals(), Keys, Values and Diff
4 days ago - For a single check in a test, the plain Java code in section 4 does the same without a library. The equals() method never looks at the order, not even for a LinkedHashMap or a TreeMap.
🌐
Reddit
reddit.com › r/javahelp › compare two list to pull out matching records
r/javahelp on Reddit: Compare two List<Map<String, Object>> to pull out matching records
August 16, 2022 -

I have two lists containing maps, the four keys are instrument, party, quantity, and entity.

If the values match between query1 and query2 results I want to add the matching maps into a list. So we can tell map1 in query1 matches map3 in query2. This is for reconciliation of data where there will be a plethora of records so essentially we want to go through each map in query1 results and compare to all maps in query2 results until there is a match. Once there is a match, we want to create a unique identifier or some sort of a pair to show that the two maps are equal and thus can be deemed a match. If we go through a full iteration and map1 in query1 does not equal any maps in query 2 we can then mark map1 in query1 a break, which would be adding the key to a break list.

Please let me know if you need more information. I've added the 3 different attempts I have tried, however none seem to make a match.

 public void reconcile(LocalDate date) {
    List<Map<String, Object>> query1Records;
    List<Map<String, Object>> query2Records;
    try (Stream<Map<String, Object>> stream = executeQuery1(date)) {
      query1Records = stream.collect(Collectors.toList());
    }
    try (Stream<Map<String, Object>> stream = executeQuery2(date)) {
      query2Records = stream.collect(Collectors.toList());
    }

//  ATTEMPT #1
    List<Map<String,Object>> unMatchedRecords = query1Records.parallelStream().filter(searchData ->
            query2Records.parallelStream().noneMatch( inputMap ->
                    searchData.entrySet().stream().noneMatch(value ->
                            inputMap.entrySet().stream().noneMatch(value1 ->
                                    (value1.getKey().equals(value.getKey()) &&
                                            value1.getValue().equals(value.getValue()))))
            )).collect(Collectors.toList());

//  ATTEMPT #2
    List<Map<String, Object>> matching = query1Records.parallelStream().filter(searchData ->
                    query2Records.parallelStream().anyMatch(inputMap ->
                            String.valueOf(searchData.get("quantity")).equals(inputMap.get("quantity"))
                                    && searchData.get("instrument").equals(inputMap.get("instrument"))
                    && searchData.get("entity").equals(inputMap.get("entity"))
                    && searchData.get("party").equals(inputMap.get("party"))))
            .collect(Collectors.toList());


//    ATTEMPT #3
    List<String> keysToReconcile = Arrays.asList("quantity", "instrument", "entity", "party");

    Function<Map<String,Object>,List<?>> getKey
            = m -> keysToReconcile.stream().map(m::get).collect(Collectors.toList());

    Map<List<?>,Map<String, Object>> inputKeys = query1Records.stream()
            .collect(Collectors.toMap(
                    getKey,
                    m -> m,
                    (a,b) -> { throw new IllegalStateException("duplicate "+a+" and "+b); },
                    LinkedHashMap::new));

    List<Map<String,Object>> matchingRecords = query2Records.stream()
            .filter(m -> inputKeys.containsKey(getKey.apply(m)))
            .collect(Collectors.toList());

    matchingRecords.forEach(m -> inputKeys.remove(getKey.apply(m)));
    List<Map<String,Object>> notMatchinRecords = new ArrayList<>(inputKeys.values());
  }
}

Top answer
1 of 2
2
Have you considered using a record to hold the information instead of a map? Would be much easier to do item-per-item comparison.
2 of 2
1
Please ensure that: Your code is properly formatted as code block - see the sidebar (About on mobile) for instructions You include any and all error messages in full You ask clear questions You demonstrate effort in solving your question/problem - plain posting your assignments is forbidden (and such posts will be removed) as is asking for or giving solutions. Trying to solve problems on your own is a very important skill. Also, see Learn to help yourself in the sidebar If any of the above points is not met, your post can and will be removed without further warning. Code is to be formatted as code block (old reddit: empty line before the code, each code line indented by 4 spaces, new reddit: https://imgur.com/a/fgoFFis ) or linked via an external code hoster, like pastebin.com, github gist, github, bitbucket, gitlab, etc. Please, do not use triple backticks (```) as they will only render properly on new reddit, not on old reddit. Code blocks look like this: public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); } } You do not need to repost unless your post has been removed by a moderator. Just use the edit function of reddit to make sure your post complies with the above. If your post has remained in violation of these rules for a prolonged period of time (at least an hour), a moderator may remove it at their discretion. In this case, they will comment with an explanation on why it has been removed, and you will be required to resubmit the entire post following the proper procedures. To potential helpers Please, do not help if any of the above points are not met, rather report the post. We are trying to improve the quality of posts here. In helping people who can't be bothered to comply with the above points, you are doing the community a disservice. I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
Top answer
1 of 4
5

If you care for performance, you should not combine a linear search with another linear search; with the resulting time complexity can’t be fixed with parallel processing when the lists get large.

You should built a data structure which allows efficient lookups first:

Map<List<?>,Map<String, Object>> inputKeys = inputRecords.stream()
    .collect(Collectors.toMap(
        m -> Arrays.asList(m.get("ID"),m.get("Email")),
        m -> m,
        (a,b) -> { throw new IllegalStateException("duplicate "+a+" and "+b); },
        LinkedHashMap::new));

List<Map<String,Object>> matchinRecords = dbRecords.stream()
    .filter(m -> inputKeys.containsKey(Arrays.asList(m.get("ID"),m.get("Email"))))
    .collect(Collectors.toList());

matchinRecords.forEach(m -> inputKeys.remove(Arrays.asList(m.get("ID"),m.get("Email"))));
List<Map<String,Object>> notMatchinRecords = new ArrayList<>(inputKeys.values());

This solution will keep the identity of the Maps.

If you are only interested in the values associated with the "Email" key, it would be much simpler:

Map<Object,Object> notMatchinRecords = inputRecords.stream()
    .collect(Collectors.toMap(
        m -> m.get("ID"),
        m -> m.get("Email"),
        (a,b) -> { throw new IllegalStateException("duplicate"); },
        LinkedHashMap::new));

Object notPresent = new Object();
Map<Object,Object> matchinRecords = dbRecords.stream()
    .filter(m -> notMatchinRecords.getOrDefault(m.get("ID"), notPresent)
                                  .equals(m.get("Email")))
    .collect(Collectors.toMap(
        m -> m.get("ID"),
        m -> m.get("Email"),
        (a,b) -> { throw new IllegalStateException("duplicate"); },
        LinkedHashMap::new));

notMatchinRecords.keySet().removeAll(matchinRecords.keySet());

System.out.println("Matching Records: " + matchinRecords.size());
matchinRecords.forEach((id,email) -> System.out.println(email));

System.out.println("Non Matching Records" + notMatchinRecords.size());
notMatchinRecords.forEach((id,email) -> System.out.println(email));

The first variant can get extended to support more/other map entries easily:

List<String> keys = Arrays.asList("ID", "Email");

Function<Map<String,Object>,List<?>> getKey
    = m -> keys.stream().map(m::get).collect(Collectors.toList());

Map<List<?>,Map<String, Object>> inputKeys = inputRecords.stream()
    .collect(Collectors.toMap(
        getKey,
        m -> m,
        (a,b) -> { throw new IllegalStateException("duplicate "+a+" and "+b); },
        LinkedHashMap::new));

List<Map<String,Object>> matchinRecords = dbRecords.stream()
    .filter(m -> inputKeys.containsKey(getKey.apply(m)))
    .collect(Collectors.toList());

matchinRecords.forEach(m -> inputKeys.remove(getKey.apply(m)));
List<Map<String,Object>> notMatchinRecords = new ArrayList<>(inputKeys.values());
2 of 4
2

You just need to add a condition in the comparison

dbRecords.parallelStream().filter(searchData -> 
                  inputRecords.parallelStream().anyMatch(inputMap ->
                                     searchData.get("Email").equals(inputMap.get("Email"))
                                     && searchData.get("id").equals(inputMap.get("id"))))
         .collect(Collectors.toList());

  • Add the same in the noneMatch().
  • Compute the average time using System.nanoTime(), it's more accurate
  • Try with and without .parallelStream() (just .stream()) because not sure it helps you)
Top answer
1 of 2
2

You could loop over the List one time and compare Maps in each list. This would cut your Big-O runtime in half -- assumes that the Maps are in the proper order (i.e., via LinkedList).

public boolean compare(List<Map<String, Object>> A, List<Map<String, Object>> B) {
    // check size first
    if (A.size() == B.size()) {
      // if the Maps are abstracted into a POJO you could implement Comparator on that POJO.  In the meantime you can sort manually
      // sort A
      Collections.sort(A, new Comparator<Map<String, String>>() {
        public int compare(final Map<String, String> o1, final Map<String, String> o2) {
            // optionally, you could use any method of sorting here... single field like an ID, multiple fields compared, sums, etc.
            return o1.get("field").compareTo(o2.get("field"));
        }
    });

      // sort B
      Collections.sort(B, new Comparator<Map<String, String>>() {
        public int compare(final Map<String, String> o1, final Map<String, String> o2) {
            return o1.get("field").compareTo(o2.get("field"));
        }
    });


        for (int i = 0; i < A.size(); i++) {
            // get map from A & B
            Map<String, Object> aMap = A.get(i);
            Map<String, Object> bMap = B.get(i);

            // check equality of Maps
            if (!aMap.equals(bMap)) {
                return false;
            }
        }
    } else {
        // Data reconciliation failed :: Data size mismatch
        return false;
    }

    // if we get here then all was good
    return true;
}
2 of 2
0

Here it is mate... The most efficient way to compare two List of Map to identify the matching and non matching records with multiple filter predicates in Java8 Streams is:

List<Map<String,String>> unMatchedRecords = dbRecords.parallelStream().filter(searchData ->
                inputRecords.parallelStream().noneMatch( inputMap ->
                        searchData.entrySet().stream().noneMatch(value ->
                                inputMap.entrySet().stream().noneMatch(value1 ->
                                        (value1.getKey().equals(value.getKey()) &&
                                                value1.getValue().equals(value.getValue()))))
                )).collect(Collectors.toList());

Note:

If <Map<String,String> used above is <Map<Object,Object> instead, don't forget to apply .toString() for .getKey() and value.getKey().

The unmatched records thus obtained, could be easily subtracted from either of the list (i.e., dbRecords or inputRecords) to retrieve the match results and the operation is swift.

Cheers,

Shubham Chauhan

🌐
Baeldung
baeldung.com › home › java › java collections › java map › comparing two hashmaps in java
Comparing Two HashMaps in Java | Baeldung
January 23, 2025 - We’ll also use Java 8 Stream API and Guava to get the detailed differences between different HashMaps. First, we’ll use Map.equals() to check if two HashMaps have the same entries:
Top answer
1 of 3
76

Quick Answer

You should use the equals method since this is implemented to perform the comparison you want. toString() itself uses an iterator just like equals but it is a more inefficient approach. Additionally, as @Teepeemm pointed out, toString is affected by order of elements (basically iterator return order) hence is not guaranteed to provide the same output for 2 different maps (especially if we compare two different maps).

Note/Warning: Your question and my answer assume that classes implementing the map interface respect expected toString and equals behavior. The default java classes do so, but a custom map class needs to be examined to verify expected behavior.

See: http://docs.oracle.com/javase/7/docs/api/java/util/Map.html

boolean equals(Object o)

Compares the specified object with this map for equality. Returns true if the given object is also a map and the two maps represent the same mappings. More formally, two maps m1 and m2 represent the same mappings if m1.entrySet().equals(m2.entrySet()). This ensures that the equals method works properly across different implementations of the Map interface.

Implementation in Java Source (java.util.AbstractMap)

Additionally, java itself takes care of iterating through all elements and making the comparison so you don't have to. Have a look at the implementation of AbstractMap which is used by classes such as HashMap:

 // Comparison and hashing

    /**
     * Compares the specified object with this map for equality.  Returns
     * <tt>true</tt> if the given object is also a map and the two maps
     * represent the same mappings.  More formally, two maps <tt>m1</tt> and
     * <tt>m2</tt> represent the same mappings if
     * <tt>m1.entrySet().equals(m2.entrySet())</tt>.  This ensures that the
     * <tt>equals</tt> method works properly across different implementations
     * of the <tt>Map</tt> interface.
     *
     * <p>This implementation first checks if the specified object is this map;
     * if so it returns <tt>true</tt>.  Then, it checks if the specified
     * object is a map whose size is identical to the size of this map; if
     * not, it returns <tt>false</tt>.  If so, it iterates over this map's
     * <tt>entrySet</tt> collection, and checks that the specified map
     * contains each mapping that this map contains.  If the specified map
     * fails to contain such a mapping, <tt>false</tt> is returned.  If the
     * iteration completes, <tt>true</tt> is returned.
     *
     * @param o object to be compared for equality with this map
     * @return <tt>true</tt> if the specified object is equal to this map
     */
    public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        Map<K,V> m = (Map<K,V>) o;
        if (m.size() != size())
            return false;

        try {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

Comparing two different types of Maps

toString fails miserably when comparing a TreeMap and HashMap though equals does compare contents correctly.

Code:

public static void main(String args[]) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("2", "whatever2");
map.put("1", "whatever1");
TreeMap<String, Object> map2 = new TreeMap<String, Object>();
map2.put("2", "whatever2");
map2.put("1", "whatever1");

System.out.println("Are maps equal (using equals):" + map.equals(map2));
System.out.println("Are maps equal (using toString().equals()):"
        + map.toString().equals(map2.toString()));

System.out.println("Map1:"+map.toString());
System.out.println("Map2:"+map2.toString());
}

Output:

Are maps equal (using equals):true
Are maps equal (using toString().equals()):false
Map1:{2=whatever2, 1=whatever1}
Map2:{1=whatever1, 2=whatever2}
2 of 3
11

As long as you override equals() on each key and value contained in the map, then m1.equals(m2) should be reliable to check for maps equality.

The same result can be obtained also by comparing toString() of each map as you suggested, but using equals() is a more intuitive approach.

May not be your specific situation, but if you store arrays in the map, may be a little tricky, because they must be compared value by value, or using Arrays.equals(). More details about this see here.

🌐
Java-forums
java-forums.org › new-java › 19327-comparing-list-maps.html
Comparing List of maps
June 10, 2009 - I have 2 lists of objects that each contain a map of parameters. In side these parameters is an ID value. I want to compare the objects within each list based on this ID Value. Is this possible in any other way than a loop inside another loop using ... I guess if it's two maps and you want to compare values you could try..
Find elsewhere
🌐
Quora
quora.com › How-can-I-compare-two-hashmaps-by-their-keys-and-values-in-Java
How to compare two hashmaps by their keys and values in Java - Quora
I know you might be tempted to use a free tool, but most of those are useless and give you almost nothing. These tools aren’t free, but they’re cheap, and they actually work. ... I just started learning Java. What IDE can a beginner like me use? ... By overriding equals() method you can compare two maps ...
🌐
Reddit
reddit.com › r/learnjava › compare two list to pull out matching records
r/learnjava on Reddit: Compare two List<Map<String, Object>> to pull out matching records
August 16, 2022 -

I have two lists containing maps, the four keys are instrument, party, quantity, and entity.

If the values match between query1 and query2 results I want to add the matching maps into a list. So we can tell map1 in query1 matches map3 in query2. This is for reconciliation of data where there will be a plethora of records so essentially we want to go through each map in query1 results and compare to all maps in query2 results until there is a match. Once there is a match, we want to create a unique identifier or some sort of a pair to show that the two maps are equal and thus can be deemed a match. If we go through a full iteration and map1 in query1 does not equal any maps in query 2 we can then mark map1 in query1 a break, which would be adding the key to a break list.

Please let me know if you need more information. I've added the 3 different attempts I have tried, however none seem to make a match.

 public void reconcile(LocalDate date) {
    List<Map<String, Object>> query1Records;
    List<Map<String, Object>> query2Records;
    try (Stream<Map<String, Object>> stream = executeQuery1(date)) {
      query1Records = stream.collect(Collectors.toList());
    }
    try (Stream<Map<String, Object>> stream = executeQuery2(date)) {
      query2Records = stream.collect(Collectors.toList());
    }

//  ATTEMPT #1
    List<Map<String,Object>> unMatchedRecords = query1Records.parallelStream().filter(searchData ->
            query2Records.parallelStream().noneMatch( inputMap ->
                    searchData.entrySet().stream().noneMatch(value ->
                            inputMap.entrySet().stream().noneMatch(value1 ->
                                    (value1.getKey().equals(value.getKey()) &&
                                            value1.getValue().equals(value.getValue()))))
            )).collect(Collectors.toList());

//  ATTEMPT #2
    List<Map<String, Object>> matching = query1Records.parallelStream().filter(searchData ->
                    query2Records.parallelStream().anyMatch(inputMap ->
                            String.valueOf(searchData.get("quantity")).equals(inputMap.get("quantity"))
                                    && searchData.get("instrument").equals(inputMap.get("instrument"))
                    && searchData.get("entity").equals(inputMap.get("entity"))
                    && searchData.get("party").equals(inputMap.get("party"))))
            .collect(Collectors.toList());


//    ATTEMPT #3
    List<String> keysToReconcile = Arrays.asList("quantity", "instrument", "entity", "party");

    Function<Map<String,Object>,List<?>> getKey
            = m -> keysToReconcile.stream().map(m::get).collect(Collectors.toList());

    Map<List<?>,Map<String, Object>> inputKeys = query1Records.stream()
            .collect(Collectors.toMap(
                    getKey,
                    m -> m,
                    (a,b) -> { throw new IllegalStateException("duplicate "+a+" and "+b); },
                    LinkedHashMap::new));

    List<Map<String,Object>> matchingRecords = query2Records.stream()
            .filter(m -> inputKeys.containsKey(getKey.apply(m)))
            .collect(Collectors.toList());

    matchingRecords.forEach(m -> inputKeys.remove(getKey.apply(m)));
    List<Map<String,Object>> notMatchinRecords = new ArrayList<>(inputKeys.values());
  }
}

Top answer
1 of 1
1
Please ensure that: Your code is properly formatted as code block - see the sidebar (About on mobile) for instructions You include any and all error messages in full - best also formatted as code block You ask clear questions You demonstrate effort in solving your question/problem - plain posting your assignments is forbidden (and such posts will be removed) as is asking for or giving solutions. If any of the above points is not met, your post can and will be removed without further warning. Code is to be formatted as code block (old reddit/markdown editor: empty line before the code, each code line indented by 4 spaces, new reddit: https://imgur.com/a/fgoFFis ) or linked via an external code hoster, like pastebin.com, github gist, github, bitbucket, gitlab, etc. Please, do not use triple backticks (```) as they will only render properly on new reddit, not on old reddit. Code blocks look like this: public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); } } You do not need to repost unless your post has been removed by a moderator. Just use the edit function of reddit to make sure your post complies with the above. If your post has remained in violation of these rules for a prolonged period of time (at least an hour), a moderator may remove it at their discretion. In this case, they will comment with an explanation on why it has been removed, and you will be required to resubmit the entire post following the proper procedures. To potential helpers Please, do not help if any of the above points are not met, rather report the post. We are trying to improve the quality of posts here. In helping people who can't be bothered to comply with the above points, you are doing the community a disservice. I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
🌐
Stack Overflow
stackoverflow.com › questions › 36259865 › how-to-compare-elements-of-a-list-with-elements-of-a-map-in-java
How to compare elements of a list with elements of a map in Java? - Stack Overflow
May 3, 2017 - I have the following code (with some sample data), and wished to check whether there is any better or performant way to compare each element of the list of map to the subsequent one: import java.util.*; public class CompareElements { private static List<Map<String, String>> sample = new ArrayList<>(0); private static int MIN = 0; private static int MAX = 10; static { populateListOfMaps(); } /* * This is the main part of the question, rest is just to generate test data..
🌐
Stack Overflow
stackoverflow.com › questions › 65859132 › efficient-way-to-compare-two-similar-maps-in-java
dictionary - Efficient way to compare two similar maps in java - Stack Overflow
Since you have to anyway write the logic to compare, the equality of two objects, I created a class with the added value field and implemented the equals logic there. I am using a HashSet for this class essentially reducing the time complexity from O(m*n) to O(max(m,n)).(Assuming sizes to be m and n). import java.util.HashMap; import java.util.HashSet; import java.util.Objects; class Main { public static void main(String[] args) { HashMap<TestClass, Integer> map1, map2; map1 = new HashMap<>(); map2 = new HashMap<>(); map1.put(new TestClass(1), 1); map1.put(new TestClass(2), 2); map2.put(new Te
🌐
Experts Exchange
experts-exchange.com › questions › 27061270 › comparing-two-Maps-in-Java-finding-missing-elements.html
Solved: comparing two Maps in Java (finding missing elements) | Experts Exchange
May 26, 2011 - Hi, I would like to efficiently compare two HashMaps in Java. The two Maps do not necessarily have the same number of elements and a given map can have no elements. When I compare the two Maps, if the two Maps are equal then it is fine. If the two are unequal I want to identify (print) the ...
Top answer
1 of 1
10

Let's summarize some of the variations on this, as the best solution only emerges after reading the other answer and the comments.

The simplified problem from the question is something like this. Given two maps:

Map<Integer, String> mapA = Map.of(1, "a", 2, "b", 3, "c")
Map<Integer, String> mapB = Map.of(5, "a", 6, "d", 7, "c")

find the keys of mapB that correspond to values that occur in both maps. The question had a start at a solution which was this (edited for clarity):

Set<Integer> result = mapB.keySet().stream()
    .filter(keyB -> mapA.keySet().stream()
                        .filter(keyA -> mapA.get(keyA).equals(mapB.get(keyB)))
                        .count() > 0)
    .collect(toSet());

Essentially this is like two nested loops that loop over the keys of each map. The inner loop gets the corresponding values for each key and counts the number of matches. If there's at least one match, key gets passed through the filter to the result.

The OP wasn't satisified with this and requested improvements, particularly about algorithmic complexity. As mentioned in the comments, the real problem might have 15,000 map entries. This algorithm is O(n^2), and it does begin to degrade noticeably with this number of mappings. There are some minor ways this could be improved, such as by using anyMatch instead of filter and count > 0, but these aren't necessary given the alternative proposed in the answer from Eritrean:

Set<Integer> result = mapB.entrySet().stream()
    .filter(entry -> mapA.values().contains(entry.getValue()))
    .map(Map.Entry::getKey)
    .collect(toSet());

This is nicer, as it uses the contains operation on mapA's values() view, replacing the inner stream of the previous solution. However, the values of a map aren't indexed, and so the only way that contains() over the values of a map can work is (potentially) to search every entry. This is somewhat better than before in that if a match is found, contains() can return immediately; but if a match isn't found, it must search all the values of the map. Therefore, on average, this variation still runs in O(n^2) time.

One way to mitigate this is to pull mapA's values into a HashSet. This will reduce the contains() check from linear time to constant time, reducing the overall complexity from O(n^2) to O(n). That would look like this:

Set<String> aValues = new HashSet<>(mapA.values());
Set<Integer> result = mapB.entrySet().stream()
    .filter(entry -> aValues.contains(entry.getValue()))
    .map(Map.Entry::getKey)
    .collect(toSet());

This is a big improvement, but it turns out that using streams isn't necessary at all. Returning to the problem statement, it has the clause "... values that occur in both maps." This is essentially doing a set intersection on the values collections. The way to do intersection in Java is with the retainAll method. That is, given two collections x and y, doing x.retainAll(y) will retain in x only those elements that also occur in y, and it will remove others. That's essentially a set intersection. To do this, retainAll typically must call contains repeatedly on y, so it's a good idea to make sure that operation is fast -- like with a HashSet.

OK, if we intersect the values collections, that gives us values -- but we want the keys. In particular, we want the keys of mapB. How do we do that?

It turns out that the values() view of a map supports removal -- which retainAll does -- and if a value is removed from it, that removes the corresponding entry from the underlying map. In this case we can start with mapB (or a copy), get its values() view, call retainAll with mapA's values that we had previously loaded into a HashSet. That leaves in mapB only the entries that have values in common with mapA. Since we're interested in the keys, not the entries, we just get the keySet() view. That code looks like this:

Set<String> aValues = new HashSet<>(mapA.values());
Map<Integer, String> mapBcopy = new HashMap<>(mapB);
mapBcopy.values().retainAll(aValues);
Set<Integer> result = mapBcopy.keySet();

This demonstrates how it's possible to use collections bulk operations on collection views to accomplish certain tasks more simply than using streams.

🌐
GeeksforGeeks
geeksforgeeks.org › java › compare-two-hashmap-objects-in-java
Compare Two HashMap Objects in Java - GeeksforGeeks
July 23, 2025 - We can compare if values contained in the map objects are the same or not by converting all map values to set using values() method and then compare values with the equals() method of the set.
🌐
Coderanch
coderanch.com › t › 589175 › java › compare-maps-java
How we compare two maps in java (Java in General forum at Coderanch)
August 6, 2012 - i am using struts with hibernate.in map code is key=1 value={place Code= 1001,place name= ""}and key=2 value={place code=1002 ,place name=""}....etc And another map also having same values Now i want to compare the place code.how we compare this ... This is a common anti-pattern that I have the misfortune to see too many times. A map that contains maps that map object properties as key-value pairs. It's a very bad practice and rather than try to figure out the answer to your original question, I suggest you choose a different data structure. Java is an object-oriented language, so what you should have is, at the least, a List of objects that encapsulate the values of 'code', 'name', etc.
🌐
Coderanch
coderanch.com › t › 517530 › java › Comparing-List-Map
Comparing List and Map (Java in General forum at Coderanch)
November 17, 2010 - If I understand both of you correctly, I don't think Wouter's suggestion will work. The values in the map are all Strings, and the things in the list are all Foos. Somewhere some code needs to call foo.getID() to convert the Foos to Strings. (I assume the ID is a String, otherwise we may need another step as well) Since we don't have closures, it probably needs to be done in a loop. Jennifer Moran wrote:Do I loop through each and compare?