Guava BiMap Use Cases and Implementation: A Complete Guide to Bidirectional Maps
Guava's BiMap is a specialized Map that enforces a strict one-to-one correspondence between keys and values, providing constant-time reverse lookups through the inverse() method while maintaining synchronized bidirectional views.
Guava's BiMap (bidirectional map) eliminates the need to manually synchronize two separate hash maps when you require lookup operations in both directions. As implemented in the google/guava repository, this collection type guarantees that no two keys map to the same value, enabling safe inverse operations and offering specialized implementations optimized for mutable, immutable, and enum-based use cases.
What is Guava BiMap?
A BiMap is a Map extension that maintains a bijection—a mathematical one-to-one relationship—between its key and value sets. Unlike standard HashMap implementations where values can collide, attempting to insert a key-value pair where the value already exists bound to a different key triggers an IllegalArgumentException. This constraint enables the BiMap to provide an inverse() method that returns a live view where keys become values and values become keys, backed by the same underlying data structure.
Core Constraints and API Features
Unique Value Enforcement
In com/google/common/collect/BiMap.java, the interface contract specifies that the put(K key, V value) operation must verify value uniqueness before insertion. If the value is already present and mapped to a different key, the implementation throws IllegalArgumentException immediately. This check ensures the bidirectional integrity required for the inverse() view to remain valid.
The forcePut Method for Overwriting
When you need to replace an existing mapping regardless of value collisions, BiMap provides forcePut(K key, V value). According to the source implementation in HashBiMap.java, this method first removes any existing entry that uses the supplied value, then inserts the new key-value pair. This atomic replacement operation is essential for updating bidirectional mappings without manual cleanup.
Bulk Operation Semantics
The putAll(Map<? extends K, ? extends V> map) operation in BiMap implementations respects the bijection constraint transactionally. As noted in the Guava source, if any entry in the bulk operation violates the unique value constraint, the operation aborts. However, earlier entries in the iteration sequence may have already been partially applied, leaving the map in an intermediate state.
The Set Values Collection
Unlike standard Map implementations where values() returns a Collection<V>, BiMap overrides this to return Set<V>. Because values must be unique by contract, they naturally form a set rather than a bag. This distinction affects return types in HashBiMap.java and other implementations, allowing value-based set operations directly on the collection view.
Real-World Use Cases for Bidirectional Maps
Reverse Lookup Without Secondary Indexes
The primary use case for BiMap involves scenarios requiring O(1) reverse lookup without maintaining a secondary data structure. For example, mapping usernames to unique user IDs allows you to retrieve an ID from a username via get(), then later recover the username from the ID via inverse().get(). This eliminates synchronization bugs common when manually maintaining two separate HashMap instances.
Encoding and Decoding Tables
URL shorteners and encoding schemes benefit from BiMap's bijection guarantees. When mapping short codes to full URLs, the uniqueness constraint prevents accidental collisions where two codes point to the same URL. The inverse() view simplifies decoding: codeToUrl.inverse().get(url) immediately returns the corresponding short code without iteration.
Enum-Based Configuration Mappings
For enum-to-value mappings where both the enum constants and target values are unique, EnumBiMap and EnumHashBiMap provide optimized alternatives to generic hash tables. As implemented in com/google/common/collect/EnumBiMap.java, these specialized classes use arrays for the forward mapping (enum ordinal indices), reducing memory overhead compared to hash buckets while maintaining the full BiMap contract.
Implementation Architecture in Guava
The BiMap Interface Contract
The BiMap interface defined in com/google/common/collect/BiMap.java extends Map and adds three critical operations: forcePut(), inverse(), and the values() override returning Set<V>. The interface establishes the mathematical bijection constraint that all implementations must enforce during mutation operations.
HashBiMap: Dual HashMap Design
HashBiMap, located in com/google/common/collect/HashBiMap.java, serves as the default mutable implementation. Internally, it maintains two HashMap instances: one for the forward direction (key → value) and one for the backward direction (value → key). All public mutation operations delegate to both maps atomically to preserve consistency. The inverse() method returns a wrapper instance that swaps the roles of these internal maps, creating a live view without data copying.
ImmutableBiMap: Compact Array Storage
For immutable bidirectional mappings, com/google/common/collect/ImmutableBiMap.java stores entries in compact arrays rather than hash tables. Because immutability prevents modifications, the implementation pre-computes the inverse mapping and stores it in a corresponding array structure. This design eliminates IllegalArgumentException risks during construction—the builder enforces uniqueness upfront—and provides faster iteration at the cost of slower construction.
Enum Optimizations
EnumBiMap (in com/google/common/collect/EnumBiMap.java) optimizes for enum keys by using an array indexed by enum ordinals for the forward mapping, while EnumHashBiMap uses a hash table for the value-to-key mapping. These implementations leverage the known finite domain of enum types to reduce memory allocation and improve lookup speed compared to generic hash-based approaches.
Working with Guava BiMap: Code Examples
Mutable Mappings with HashBiMap
The following example demonstrates creating a mutable bidirectional map, handling duplicate values, and using the inverse view:
// Create a mutable HashBiMap
BiMap<String, Integer> userId = HashBiMap.create();
userId.put("alice", 1001);
userId.put("bob", 1002);
// Normal put throws if value already bound
// userId.put("charlie", 1001); // IllegalArgumentException
// Force‑put replaces the previous entry with the same value
userId.forcePut("charlie", 1001); // "alice" is removed
// Reverse lookup via the inverse view
BiMap<Integer, String> idToUser = userId.inverse();
System.out.println(idToUser.get(1002)); // prints "bob"
// The inverse view stays in sync with the original map
userId.put("dave", 1003);
System.out.println(idToUser.get(1003)); // prints "dave"
// Removing an entry via either side updates both maps
idToUser.remove(1002); // removes "bob"
System.out.println(userId.containsKey("bob")); // false
Immutable Bidirectional Maps
For configuration data that never changes, ImmutableBiMap provides type-safe bidirectional mappings with minimal memory overhead:
ImmutableBiMap<String, Integer> colorCodes = ImmutableBiMap.of(
"red", 1,
"green", 2,
"blue", 3);
// Inverse is also immutable
ImmutableBiMap<Integer, String> codeToColor = colorCodes.inverse();
System.out.println(codeToColor.get(2)); // prints "green"
// Attempting to modify either map throws UnsupportedOperationException
// colorCodes.put("yellow", 4); // RuntimeException
Summary
-
Guava BiMap enforces a one-to-one relationship between keys and values, throwing
IllegalArgumentExceptionwhen duplicate values are inserted via standardput(). -
The
inverse()method returns a live view backed by the same underlying data, enabling constant-time reverse lookups without maintaining secondary data structures. -
forcePut()bypasses uniqueness checks by atomically removing conflicting entries before insertion, useful for updating existing mappings. -
HashBiMapimplements the mutable contract using dualHashMapinstances, whileImmutableBiMapuses compact arrays for memory-efficient immutable bidirectional maps. -
Specialized implementations
EnumBiMapandEnumHashBiMapoptimize for enum keys using array-based forward mappings.
Frequently Asked Questions
What happens when you try to put a duplicate value in a Guava BiMap?
Attempting to put() a key-value pair where the value already exists bound to a different key throws IllegalArgumentException. This behavior, enforced in all mutable implementations like HashBiMap, protects the bijection invariant required for the inverse() view to function correctly. To replace existing mappings regardless of value collisions, use the forcePut() method instead.
How does BiMap inverse() work?
The inverse() method returns a live BiMap view where the original keys become values and original values become keys. In HashBiMap, this is implemented as a lightweight wrapper that swaps the internal forward and backward hash maps. Changes made to either the original map or its inverse are immediately visible in both directions because they share the same underlying data structures.
What is the difference between HashBiMap and ImmutableBiMap?
HashBiMap is a mutable implementation using two HashMap instances to support read and write operations with O(1) complexity. ImmutableBiMap is an immutable, thread-safe implementation that stores entries in compact arrays and pre-computes its inverse during construction. The immutable variant cannot be modified after creation and enforces uniqueness constraints during the build phase rather than at insertion time.
When should I use EnumBiMap instead of HashBiMap?
Use EnumBiMap when both keys and values are enum types, as it replaces hash tables with arrays indexed by enum ordinals, significantly reducing memory overhead and improving cache locality. EnumHashBiMap is appropriate when only the keys are enums and values are arbitrary objects, using an array for the forward mapping but a hash table for reverse lookups. Both provide better performance than generic HashBiMap for enum-constrained domains.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →