Ova

How to store duplicate keys in Java?

Published in Java Collections 7 mins read

While standard Java Map implementations (like HashMap, TreeMap) do not allow "duplicate keys" – adding a value for an existing key will simply overwrite the previous value – you can effectively store multiple values associated with a single key. This is commonly achieved by mapping a key to a collection of values, often referred to as a "multimap" or "multi-valued map."


How to Store Multiple Values for the Same Key in Java

Java provides several approaches to handle scenarios where you need a single key to point to more than one value. This capability is essential in many applications, from indexing data to managing configurations.

1. Using a Map with a Collection as its Value (Manual Approach)

This is the most straightforward and built-in way to create a multi-valued map without external libraries. You use a standard java.util.Map where the value type is a Collection (e.g., List or Set).

1.1 Map<K, List<V>> (Allows Duplicate Values for a Key)

When you need to maintain the order of values or allow identical values associated with the same key, a List is the appropriate choice.

Example:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ListMultiValueMap {
    public static void main(String[] args) {
        Map<String, List<String>> cityResidents = new HashMap<>();

        // Add residents for "New York"
        cityResidents.computeIfAbsent("New York", k -> new ArrayList<>()).add("Alice");
        cityResidents.computeIfAbsent("New York", k -> new ArrayList<>()).add("Bob");
        cityResidents.computeIfAbsent("New York", k -> new ArrayList<>()).add("Alice"); // Duplicate value allowed

        // Add residents for "London"
        cityResidents.computeIfAbsent("London", k -> new ArrayList<>()).add("Charlie");
        cityResidents.computeIfAbsent("London", k -> new ArrayList<>()).add("David");

        System.out.println("New York residents: " + cityResidents.get("New York")); // Output: [Alice, Bob, Alice]
        System.out.println("London residents: " + cityResidents.get("London"));     // Output: [Charlie, David]
        System.out.println("Chicago residents: " + cityResidents.get("Chicago"));   // Output: null (or empty list if handled)
    }
}
  • computeIfAbsent(): This method is highly useful for this pattern. It checks if a key exists; if not, it computes and puts a new value (an empty ArrayList in this case) and then adds the desired value to it. If the key already exists, it simply retrieves the existing list and adds the value.

1.2 Map<K, Set<V>> (Ensures Unique Values for a Key)

If you need to ensure that each value associated with a specific key is unique (i.e., you don't want duplicate values for the same key), then a Set is the better choice.

Example:

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class SetMultiValueMap {
    public static void main(String[] args) {
        Map<String, Set<String>> userRoles = new HashMap<>();

        // Add roles for "adminUser"
        userRoles.computeIfAbsent("adminUser", k -> new HashSet<>()).add("ADMIN");
        userRoles.computeIfAbsent("adminUser", k -> new HashSet<>()).add("REPORT_VIEWER");
        userRoles.computeIfAbsent("adminUser", k -> new HashSet<>()).add("ADMIN"); // Duplicate value added, but ignored by HashSet

        // Add roles for "regularUser"
        userRoles.computeIfAbsent("regularUser", k -> new HashSet<>()).add("REPORT_VIEWER");

        System.out.println("Admin user roles: " + userRoles.get("adminUser")); // Output: [REPORT_VIEWER, ADMIN] (order may vary)
        System.out.println("Regular user roles: " + userRoles.get("regularUser"));
    }
}
  • When you use a HashSet for the internal collection, any attempt to add an identical value for the same key will be ignored, maintaining uniqueness among the values associated with that key.

2. Using Third-Party Libraries (Multimap)

For more robust and convenient handling of multi-valued maps, several third-party libraries provide dedicated Multimap interfaces and implementations. These libraries abstract away the manual management of collections, making the code cleaner and less error-prone.

2.1 Google Guava's Multimap

Guava's Multimap is a powerful and widely used solution. It offers various implementations tailored for different needs regarding order and uniqueness.

Key Features of Guava Multimap:

  • Convenient API: Simplifies adding, retrieving, and removing multiple values.
  • Performance: Optimized implementations.
  • Readability: Reduces boilerplate code compared to manual Map<K, Collection<V>>.

Common Multimap Implementations:

  • ArrayListMultimap: Allows duplicate values and preserves insertion order for values associated with a key.
  • HashMultimap: Values for a given key are stored in a HashSet, ensuring uniqueness but not preserving order.
  • LinkedHashMultimap: Values for a given key are stored in a LinkedHashSet, ensuring uniqueness and preserving insertion order.
  • TreeMultimap: Values for a given key are stored in a TreeSet, keeping them sorted.

Example using ArrayListMultimap (allowing duplicate values):

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

public class GuavaMultiMapExample {
    public static void main(String[] args) {
        Multimap<String, String> studentCourses = ArrayListMultimap.create();

        studentCourses.put("John Doe", "Math");
        studentCourses.put("John Doe", "Physics");
        studentCourses.put("Jane Smith", "Chemistry");
        studentCourses.put("John Doe", "Math"); // Duplicate value allowed by ArrayListMultimap

        System.out.println("John Doe's courses: " + studentCourses.get("John Doe")); // Output: [Math, Physics, Math]
        System.out.println("Jane Smith's courses: " + studentCourses.get("Jane Smith"));
        System.out.println("All entries: " + studentCourses.entries());
    }
}

(To use Guava, you need to add it as a dependency in your project's pom.xml or build.gradle file).
Learn more about Guava Multimaps

2.2 Apache Commons Collections MultiValuedMap

Another excellent option is the MultiValuedMap from Apache Commons Collections. Similar to Guava's Multimap, it provides a dedicated interface and various implementations.

Example using HashSetValuedHashMap (ensuring unique values):

// Dependency: org.apache.commons:commons-collections4

import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import java.util.Collection;

public class ApacheMultiValueMapExample {
    public static void main(String[] args) {
        // HashSetValuedHashMap uses a HashSet internally for values,
        // which means it will automatically drop duplicate values for the same key.
        MultiValuedMap<String, String> userPermissions = new HashSetValuedHashMap<>();

        userPermissions.put("editor", "create");
        userPermissions.put("editor", "edit");
        userPermissions.put("viewer", "view");
        userPermissions.put("editor", "create"); // This duplicate value will be ignored by HashSetValuedHashMap

        Collection<String> editorPermissions = userPermissions.get("editor");
        System.out.println("Editor permissions: " + editorPermissions); // Output: [edit, create] (order may vary as it's a Set)

        Collection<String> viewerPermissions = userPermissions.get("viewer");
        System.out.println("Viewer permissions: " + viewerPermissions); // Output: [view]
    }
}
  • The HashSetValuedHashMap from Apache Commons Collections, as its name suggests, stores the values for each key in a HashSet. This automatically ensures that if you add the exact same value for the same key multiple times, only one instance of that value will be kept within the key's associated collection.

Comparison of Approaches

Here's a quick overview to help you choose the right approach:

Feature/Approach Manual Map<K, List<V>> Manual Map<K, Set<V>> Guava Multimap (e.g., ArrayListMultimap) Apache Commons MultiValuedMap (e.g., HashSetValuedHashMap)
Duplicate Values per Key Allowed Not Allowed Allowed (e.g., ArrayListMultimap) Depends on implementation (e.g., HashSetValuedHashMap drops duplicates)
Order of Values per Key Preserved Not preserved (e.g., HashSet) Preserved (e.g., ArrayListMultimap) Not preserved (e.g., HashSetValuedHashMap)
Boilerplate Code More (manual collection management) More (manual collection management) Less (dedicated API) Less (dedicated API)
Dependencies None (built-in Java) None (built-in Java) Guava library Apache Commons Collections library
Flexibility High (control over collection type) High (control over collection type) Very High (many Multimap implementations) High (various MultiValuedMap implementations)
Use Case Simple, small projects, no external dependencies Simple, small projects, uniqueness required Complex multi-mapping, large projects, performance, convenience Complex multi-mapping, similar to Guava

Conclusion

When you need to store multiple values for the same key in Java, you have excellent options:

  1. Manual Map<K, Collection<V>>: Ideal for simpler cases or when avoiding external dependencies is a priority. Choose List for ordered, duplicate-allowing values, or Set for unique, unordered values.
  2. Guava Multimap or Apache Commons MultiValuedMap: Recommended for more complex scenarios, larger projects, or when you desire a more concise and robust API. These libraries offer specialized implementations that simplify managing the value collections and provide specific behaviors regarding value order and uniqueness.

The choice largely depends on project complexity, dependency requirements, and whether you need to allow duplicate values for a given key, and whether order matters.