Inverting a map in Java means building a new map whose keys are the values of the original map and whose values are its keys, and the shortest code for it is map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)). That one-liner works only when every value is unique. When two keys share a value, we either pick one key with a merge function or keep all of them in a Map<V, List<K>>.
We invert maps to look up a key by its value, for example to find a language code from the language name, or to turn a user-to-role map into a role-to-users map for an admin screen.
The following example inverts a map of language codes and looks up a code by name.
Map<String, String> languages = Map.of("en", "English", "fr", "French", "de", "German");
Map<String, String> codesByName = languages.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
String french = codesByName.get("French"); // "fr"
String german = codesByName.get("German"); // "de"
Notice that the inverted map is a new, independent map, and the original stays unchanged. We start with maps that have unique values, move on to duplicate values and the three ways to handle them, and finish with the bidirectional maps from Guava and Apache Commons Collections that keep both directions in sync.
1. What Is an Inverted Map?
An inverted map of a Map<K, V> is a new Map<V, K> in which every entry k=v becomes v=k. Because map keys must be unique, the inversion is lossless only when the original values are unique. When values repeat, several entries compete for one key, and the code must decide what happens.

In the diagram, two users share the admin role, so the three strategies give three different inverted maps from the same input.
2. Inverting a Map With Unique Values
When every value appears only once, the inverted map has the same number of entries as the original. The loop and the stream version of a plain inversion both assume that, and section 3 covers what goes wrong otherwise.
2.1. Swapping Entries in a for Loop
The plain loop iterates over the entries and calls put() with the key and value swapped. It needs no streams, and it is the simplest version to debug.
Map<String, String> languages = new TreeMap<>(Map.of("en", "English", "fr", "French"));
Map<String, String> inverted = new HashMap<>();
for (Map.Entry<String, String> entry : languages.entrySet()) {
inverted.put(entry.getValue(), entry.getKey());
}
String code = inverted.get("English"); // "en"
int size = inverted.size(); // 2
When a value repeats, put() replaces the earlier key without any warning, so the loop keeps the last key in iteration order. Since the iteration order of a HashMap is not defined, we do not know in advance which key survives.
2.2. Swapping Keys and Values With Collectors.toMap()
The Stream API version streams the entry set and passes the swapped getters to Collectors.toMap(). Unlike the loop, toMap() refuses to lose data, so a duplicate value throws IllegalStateException instead of overwriting.
Map<String, String> userRoles = new TreeMap<>(Map.of("alice", "admin", "bob", "editor", "carol", "admin"));
Map<String, String> usersByRole = userRoles.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); // IllegalStateException: Duplicate key admin
The full message is Duplicate key admin (attempted merging values alice and carol), which names the value that repeats. The collector also throws NullPointerException when an original key is null, because that key becomes a value of the new map, and toMap() does not accept null values.
Map<String, String> withNullKey = new HashMap<>();
withNullKey.put(null, "unknown");
Map<String, String> flipped = withNullKey.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); // NullPointerException
2.3. Keeping the Order of the Original Map
The two-argument toMap() returns a HashMap in practice, so the inverted entries come out in no particular order. The four-argument version takes a map supplier, and LinkedHashMap::new keeps the order of the original map while TreeMap::new sorts by the new keys.
Map<String, String> languages = new TreeMap<>(Map.of("en", "English", "fr", "French", "de", "German"));
Map<String, String> sameOrder = languages.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey, (a, b) -> a, LinkedHashMap::new)); // {German=de, English=en, French=fr}
Map<String, String> sortedByName = languages.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey, (a, b) -> a, TreeMap::new)); // {English=en, French=fr, German=de}
The four-argument version requires a merge function as the third argument, even when we know the values are unique. A function such as (a, b) -> a is a harmless placeholder in that case.
3. Inverting a Map With Duplicate Values
A content management app stores the role of each user in a Map<String, String>. The admin screen needs the opposite view, which lists all users per role, and several users share the same role. Here, a plain inversion would lose users, so we choose one of three strategies.
| Strategy | Code | Result for alice, bob, carol |
|---|---|---|
| Fail on duplicates | toMap(getValue, getKey) | IllegalStateException |
| Keep one key | toMap(getValue, getKey, (first, second) -> first) | {admin=alice, editor=bob} |
| Keep all keys | groupingBy(getValue, mapping(getKey, toList())) | {admin=[alice, carol], editor=[bob]} |
3.1. Keeping One Key With a Merge Function
The third argument of toMap() is a merge function, which receives the existing value and the new value for the same key and returns the one to keep. With an ordered source such as a TreeMap and a LinkedHashMap supplier, “first” and “last” have a defined meaning, because they follow the order of the original map.
Map<String, String> userRoles = new TreeMap<>(Map.of("alice", "admin", "bob", "editor", "carol", "admin"));
Map<String, String> firstUser = userRoles.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey, (first, second) -> first, LinkedHashMap::new)); // {admin=alice, editor=bob}
Map<String, String> lastUser = userRoles.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey, (first, second) -> second, LinkedHashMap::new)); // {admin=carol, editor=bob}
Map<String, String> joined = userRoles.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey, (a, b) -> a + "," + b, LinkedHashMap::new)); // {admin=alice,carol, editor=bob}
Joining the keys into one string works for display, but it turns the keys into text that we have to split again later. When the program needs the individual keys, a list per value is the better result.
3.2. Keeping All Keys in a Loop With computeIfAbsent()
The loop version of a multimap uses computeIfAbsent(), which creates the list the first time a role appears and returns the existing list after that. It replaces the older containsKey(), get() and put() sequence with one call.
Map<String, String> userRoles = new TreeMap<>(Map.of("alice", "admin", "bob", "editor", "carol", "admin"));
Map<String, List<String>> usersByRole = new TreeMap<>();
for (Map.Entry<String, String> entry : userRoles.entrySet()) {
usersByRole.computeIfAbsent(entry.getValue(), role -> new ArrayList<>()).add(entry.getKey());
}
Map<String, List<String>> result = usersByRole; // {admin=[alice, carol], editor=[bob]}
3.3. Keeping All Keys With Collectors.groupingBy()
The stream version uses Collectors.groupingBy() with the original value as the classifier, and Collectors.mapping() to collect only the original keys instead of whole entries. The result type is Map<V, List<K>>.
Map<String, String> userRoles = new TreeMap<>(Map.of("alice", "admin", "bob", "editor", "carol", "admin"));
Map<String, List<String>> usersByRole = userRoles.entrySet().stream().collect(Collectors.groupingBy(Map.Entry::getValue, TreeMap::new, Collectors.mapping(Map.Entry::getKey, Collectors.toList()))); // {admin=[alice, carol], editor=[bob]}
Map<String, Long> usersPerRole = userRoles.entrySet().stream().collect(Collectors.groupingBy(Map.Entry::getValue, TreeMap::new, Collectors.counting())); // {admin=2, editor=1}
Use groupingBy() whenever duplicate values are possible and every key matters, because it never throws on duplicates and never drops a key. The second statement shows that the downstream collector can also count the keys, which is often all a dashboard needs.
4. Inverting With Guava BiMap and Multimap
When an app looks up in both directions all the time, inverting a map again after every change is wasteful. Guava provides two types for the two cases, BiMap for unique values and Multimap for repeated values. The examples use Guava 33.7.2.
4.1. Using BiMap for Unique Values
A BiMap keeps its values unique, and inverse() returns a live view with keys and values swapped. A change through either view shows up in the other one. Putting an existing value under a new key throws IllegalArgumentException, and forcePut() removes the old entry first.
BiMap<String, String> languages = HashBiMap.create(Map.of("en", "English", "fr", "French"));
BiMap<String, String> byName = languages.inverse();
String fr = byName.get("French"); // "fr"
languages.put("es", "Spanish");
String es = byName.get("Spanish"); // "es"
String old = languages.put("eng", "English"); // IllegalArgumentException: value already present: English
String replaced = languages.forcePut("eng", "English"); // null
String newCode = byName.get("English"); // "eng"
The forcePut() call returns null because the key eng had no previous value, and it removed the en entry without an error. HashBiMap keeps two hash tables internally and accepts null keys and values, whereas ImmutableBiMap rejects null.
4.2. Using Multimaps.invertFrom() for Duplicate Values
For a map with repeated values, Guava inverts into a Multimap, which stores several values per key. The method Multimaps.forMap() wraps our Map as a multimap, and Multimaps.invertFrom() copies the swapped entries into a destination multimap that we create.
Map<String, String> userRoles = new TreeMap<>(Map.of("alice", "admin", "bob", "editor", "carol", "admin"));
ListMultimap<String, String> usersByRole = Multimaps.invertFrom(Multimaps.forMap(userRoles), ArrayListMultimap.create());
List<String> admins = usersByRole.get("admin"); // [alice, carol]
List<String> viewers = usersByRole.get("viewer"); // []
A Multimap returns an empty collection for a missing key instead of null, so the calling code needs no null check.
5. Using Apache Commons Collections
Apache Commons Collections 4.5.0 offers the same two tools, a bidirectional BidiMap and a one-time MapUtils.invertMap().
5.1. Using BidiMap
The BidiMap interface has four main implementations. Each one keeps keys and values unique and returns the inverted view from inverseBidiMap().
- DualHashBidiMap uses two HashMap instances, so it has no defined order.
- DualLinkedHashBidiMap uses two LinkedHashMap instances and keeps insertion order.
- DualTreeBidiMap uses two TreeMap instances and keeps both directions sorted.
- TreeBidiMap uses a single red-black tree, so it stores each entry once, and requires keys and values that implement Comparable.
BidiMap<String, String> languages = new DualHashBidiMap<>(Map.of("en", "English", "fr", "French"));
String code = languages.getKey("French"); // "fr"
String sameCode = languages.inverseBidiMap().get("French"); // "fr"
languages.put("eng", "English");
boolean oldKeyKept = languages.containsKey("en"); // false
Unlike Guava’s BiMap, a BidiMap does not throw on a repeated value. The later put() wins and removes the older key, which behaves like Guava’s forcePut().
5.2. Using MapUtils.invertMap()
The static method MapUtils.invertMap() returns a new HashMap with keys and values swapped, which is the same as the loop in section 2.1.
Map<String, String> languages = Map.of("en", "English", "fr", "French");
Map<String, String> byName = MapUtils.invertMap(languages);
String en = byName.get("English"); // "en"
When the original map has duplicate values, the result contains only one of the competing keys, and which key survives is not specified. We use invertMap() only for maps with unique values, or switch to groupingBy().
6. Choosing an Approach
The right approach depends on two facts about the data. The first is whether values can repeat, and the second is whether we need the inverse once or keep looking up in both directions.
| Situation | Approach |
|---|---|
| Unique values, one-time inversion | Collectors.toMap(getValue, getKey) or the for loop |
| Unique values, order matters | toMap() with LinkedHashMap::new or TreeMap::new |
| Duplicate values, one key is enough | toMap() with a merge function |
| Duplicate values, keep every key | groupingBy() with mapping(), or Guava Multimaps.invertFrom() |
| Lookups in both directions, data changes | Guava BiMap or Commons BidiMap |
Every one-time approach walks over all entries, so the work grows with the map size. A BiMap or BidiMap pays that cost on every put() instead, which is worthwhile only when both directions are read often. Both kinds of map build on the standard map types of the Java Collections Framework.
7. Inverting a Map FAQs
Searches about swapping map keys and values often continue with key lookups, built-in support, the IllegalStateException and in-place inversion.
7.1. How Do We Get a Key From a Value Without Inverting the Whole Map?
We stream the entries and filter by value. For a single lookup that is cheaper than building a whole inverted map, but it scans the map on every call.
Map<String, String> languages = Map.of("en", "English", "fr", "French");
Optional<String> code = languages.entrySet().stream().filter(e -> e.getValue().equals("French")).map(Map.Entry::getKey).findFirst(); // Optional[fr]
7.2. Does Java Have a Built-In Bidirectional Map?
No. The JDK has no BiMap type, so we either keep two maps in sync ourselves or use Guava’s BiMap or Commons Collections’ BidiMap.
7.3. Why Does Collectors.toMap() Throw IllegalStateException When Inverting?
The original map has at least one value that appears under two keys, and toMap() without a merge function refuses to pick one. We add a merge function as the third argument, or use groupingBy() to keep both keys.
7.4. Can We Invert a Map in Place?
No. A map cannot change its key and value types, and swapping entries one by one would overwrite entries that we have not read yet. We always build a new map, or use a BiMap view.
8. Conclusion
Inverting a map with unique values takes one Collectors.toMap() call with the getters swapped, plus a map supplier when the order matters. With duplicate values, toMap() throws IllegalStateException, so we either choose one key with a merge function or keep all keys with groupingBy() and mapping().
For data that we read in both directions, Guava’s BiMap and Commons Collections’ BidiMap keep the inverse view in sync. They differ on repeated values, because BiMap.put() throws while BidiMap.put() replaces the older key.
9. References
- Collectors (Java SE 25 API)
- Map.computeIfAbsent() (Java SE 25 API)
- Guava BiMap
- Guava Multimaps
- Commons Collections MapUtils
Happy Learning !!