1. Overview

In this quick tutorial, we’ll have a look at the MultiValuedMap interface provided in the Apache Commons Collections library*.*

MultiValuedMap provides a simple API for mapping each key to a collection of values in Java. It's the successor to org.apache.commons.collections4.MultiMap, which was deprecated in Commons Collection 4.1.

2. Maven Dependency

For Maven projects, we need to add the commons-collections4 dependency:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.2</version>
</dependency>

3. Adding Elements into a MultiValuedMap

We can add elements using the put and putAll methods.

Let's start by creating an instance of MultiValuedMap:

MultiValuedMap<String, String> map = new ArrayListValuedHashMap<>();

Next, let's see how we can add elements one at a time using the put method:

map.put("fruits", "apple");
map.put("fruits", "orange");

In addition, let's add some elements using the putAll method, which maps a key to multiple elements in a single call:

map.putAll("vehicles", Arrays.asList("car", "bike"));
assertThat((Collection<String>) map.get("vehicles"))
  .containsExactly("car", "bike");

4. Retrieving Elements from a MultiValuedMap

MultiValuedMap provides methods to retrieve keys, values, and key-value mappings. Let's take a look at each of those.

4.1. Get All Values of a Key

To get all values associated with a key, we can use the get method, which returns a Collection:

assertThat((Collection<String>) map.get("fruits"))
  .containsExactly("apple", "orange");

4.2. Get All Key-Value Mappings

Or, we can use the entries method to get a Collection of all key-value mappings contained in the map:

Collection<Map.Entry<String, String>> entries = map.entries();

4.3. Get All Keys

There are two methods for retrieving all the keys contained in a MultiValuedMap.

Let's use the keys method to get a MultiSet view of the keys:

MultiSet<String> keys = map.keys();
assertThat(keys).contains("fruits", "vehicles");

Alternatively, we can get a Set view of the keys using the keySet method:

Set<String> keys = map.keySet();
assertThat(keys).contains("fruits", "vehicles");

4.4. Get All Values of a Map

Finally, if we want to get a Collection view of all values contained in the map, we can use the values method:

Collection<String> values = map.values();
assertThat(values).contains("apple", "orange", "car", "bike");

5. Removing Elements from a MultiValuedMap

Now, let's look at all the methods for removing elements and key-value mappings.

5.1. Remove All Elements Mapped to a Key

First, let's see how to remove all values associated with a specified key using the remove method:

Collection<String> removedValues = map.remove("fruits");
assertThat(map.containsKey("fruits")).isFalse();
assertThat(removedValues).contains("apple", "orange");

This method returns a Collection view of the removed values.

5.2. Remove a Single Key-Value Mapping

Now, suppose we have a key mapped to multiple values, but we want to remove only one of the mapped values, leaving the others. We can easily do this using the removeMapping method:

boolean isRemoved = map.removeMapping("fruits","apple");
assertThat(map.containsMapping("fruits","apple")).isFalse();

5.3. Remove All Key-Value Mappings

And finally, we can use the clear method to remove all mappings from the map:

map.clear();
assertThat(map.isEmpty()).isTrue();

6. Checking Elements from a MultiValuedMap

Next, let's take a look at the various methods for checking whether a specified key or value exists in our map.

6.1. Check If a Key Exists

To find out whether our map contains a mapping for a specified key, we can use the containsKey method:

assertThat(map.containsKey("vehicles")).isTrue();

6.2. Check If a Value Exists

Next, suppose we want to check if at least one key in our map contains a mapping for a particular value. We can do this using the containsValue method:

assertThat(map.containsValue("orange")).isTrue();

6.3. Check If a Key-Value Mapping Exists

Similarly, if we want to check whether a map contains a mapping for a specific key and value pair, we can use the containsMapping method:

assertThat(map.containsMapping("fruits","orange")).isTrue();

6.4. Check If a Map Is Empty

To check if a map does not contain any key-value mappings at all, we can use the isEmpty method:

assertThat(map.isEmpty()).isFalse;

6.5. Check the Size of a Map

Finally, we can use the size method to get the total size of the map. When a map has keys with multiple values, then the total size of the map is the count of all the values from all keys:

assertEquals(4, map.size());

7. Implementations

The Apache Commons Collections Library also provides multiple implementations of this interface. Let's have a look at them.

7.1. ArrayListValuedHashMap

An ArrayListValuedHashMap uses an ArrayList internally for storing the values associated with each key, so it allows duplicate key-values pairs:

MultiValuedMap<String, String> map = new ArrayListValuedHashMap<>();
map.put("fruits", "apple");
map.put("fruits", "orange");
map.put("fruits", "orange");
assertThat((Collection<String>) map.get("fruits"))
  .containsExactly("apple", "orange", "orange");

Now, it's worth noting that this class is not thread-safe. Therefore, if we want to use this map from multiple threads, we must be sure to use proper synchronization.

7.2. HashSetValuedHashMap

A HashSetValuedHashMap uses a HashSet for storing the values for each given key. Therefore, it doesn't allow duplicate key-value pairs.

Let's see a quick example, where we add the same key-value mapping twice:

MultiValuedMap<String, String> map = new HashSetValuedHashMap<>();
map.put("fruits", "apple");
map.put("fruits", "apple");
assertThat((Collection<String>) map.get("fruits"))
  .containsExactly("apple");

Notice how, unlike our previous example that used ArrayListValuedHashMap, the HashSetValuedHashMap implementation ignores the duplicate mapping.

The HashSetValuedHashMap class is also not thread-safe.

7.3. UnmodifiableMultiValuedMap

The UnmodifiableMultiValuedMap is a decorator class that is useful when we need an immutable instance of a MultiValuedMap – that is, it shouldn't allow further modifications:

@Test(expected = UnsupportedOperationException.class)
public void givenUnmodifiableMultiValuedMap_whenInserting_thenThrowingException() {
    MultiValuedMap<String, String> map = new ArrayListValuedHashMap<>();
    map.put("fruits", "apple");
    map.put("fruits", "orange");
    MultiValuedMap<String, String> immutableMap =
      MultiMapUtils.unmodifiableMultiValuedMap(map);
    immutableMap.put("fruits", "banana"); // throws exception
}

And again, it’s worth noting that modifying the final put will result in an UnsupportedOperationException.

8. Conclusion

We've seen various methods of the MultiValuedMap interface from the Apache Commons Collections library. In addition, we've explored a few popular implementations.

And, as always, the full source code is available over on Github.