Back to Blog
Java

Creating a Java Immutable Map

java immutable map: Learn how to create immutable maps in Java using Map.of, Map.copyOf, and Collections.unmodifiableMap, and understand the differences in behavior an...

immutable collectionsMap.ofunmodifiable mapJava collectionsfunctional programming
A Java immutable map concept showing a locked map with keys and values, representing unmodifiable collections.

When you need to pass a configuration map around an application without allowing it to be modified, the Java standard library gives you several ways to create a java immutable map. The choice affects not only whether mutation is blocked, but also how the map behaves at runtime and how much memory it uses.

The Problem with Mutable Maps

A regular HashMap is mutable by design. Every reference to the map can call put, remove, or clear, which makes it easy to introduce subtle bugs when the map is shared across components. For example, a service that reads a configuration map might accidentally modify it, breaking other parts of the application that rely on the original values.

Map<String, String> config = new HashMap<>(); config.put("timeout", "30"); // Later, another component can modify it: config.put("timeout", "60");

Once the map is passed to multiple methods, there is no way to enforce that it stays unchanged. The standard solution is to create an immutable view or a truly immutable map.

Creating an Immutable Map with Map.of

Java 9 introduced Map.of as a static factory method for creating small immutable maps. It accepts key-value pairs and returns a map that cannot be modified. Attempting to call put, remove, or replace throws an UnsupportedOperationException.

Map<String, Integer> scores = Map.of( "alice", 10, "bob", 20, "carol", 30 );

Map.of has overloads for up to 10 entries. If you need more, use Map.ofEntries, which takes an array of Map.Entry objects.

Map<String, Integer> large = Map.ofEntries( Map.entry("alice", 10), Map.entry("bob", 20), // ... more entries );

Both Map.of and Map.ofEntries reject null keys and values. This is a deliberate design choice: an immutable map cannot contain a null key because containsKey would be ambiguous, and a null value is often a sign of an incomplete configuration.

Using Map.copyOf for Existing Maps

If you already have a map and want an immutable copy, Map.copyOf (added in Java 10) creates an independent immutable map. It copies all entries from the source map, so later changes to the original do not affect the copy.

Map<String, String> original = new HashMap<>(); original.put("host", "localhost"); original.put("port", "8080"); Map<String, String> immutable = Map.copyOf(original); original.put("port", "9090"); // immutable still has port=8080

Like Map.of, Map.copyOf throws NullPointerException if the source map contains a null key or value. It also does not preserve the iteration order of the source map; the order is unspecified.

Wrapping with Collections.unmodifiableMap

Before Java 9, the standard way to create an unmodifiable map was Collections.unmodifiableMap. This method returns a view of the original map. The view does not allow direct modification, but the underlying map can still be changed through its original reference.

Map<String, String> mutable = new HashMap<>(); mutable.put("key", "value"); Map<String, String> unmodifiable = Collections.unmodifiableMap(mutable); // This throws UnsupportedOperationException unmodifiable.put("other", "value"); // But this is allowed: mutable.put("other", "value"); // Now unmodifiable sees the change

This is a view, not a copy. If the original map is modified after the wrapper is created, the unmodifiable view reflects those changes. This behavior is useful when you want to expose a read-only view of a map that is still owned by another component, but it is not a true immutable map.

Comparing the Three Approaches

The following table summarizes the key differences:

FeatureMap.of / Map.ofEntriesMap.copyOfCollections.unmodifiableMap
Introduced inJava 9Java 10Java 1.2
Null keys/valuesNot allowedNot allowedAllowed (but underlying map must support)
Independent copyYesYesNo, it is a view
Iteration orderUnspecifiedUnspecifiedDepends on underlying map
Best forSmall fixed setsCopying an existing mapExposing a read-only view of a mutable map

Map.of and Map.copyOf produce maps that are both immutable and independent. Collections.unmodifiableMap only prevents direct modification through the wrapper; it does not protect against changes to the original map.

Performance and Memory Considerations

Immutable maps created by Map.of and Map.copyOf are typically more memory-efficient than a HashMap because they do not need a resizable array of buckets. The implementation stores entries in a compact array and uses a hash function to locate them. This reduces overhead, especially for small maps.

There is also a performance benefit in terms of thread safety. An immutable map can be safely shared across threads without synchronization, because there is no state to corrupt. This is a significant advantage in concurrent applications, where mutable maps require careful locking or concurrent collections.

However, the lack of mutation means that every change requires creating a new map. If you need to update a map frequently, an immutable map is not the right choice. The overhead of copying the entire map on each change outweighs the safety benefits.

Choosing the Right Approach for Your Use Case

Use Map.of when you need a small, fixed set of key-value pairs that are known at compile time. It is concise and efficient for configuration constants, lookup tables, or default values.

Use Map.copyOf when you have an existing map that you want to protect from future changes. This is common when receiving a map from an external source, such as a method parameter or a deserialized object, and you want to ensure it cannot be modified later.

Use Collections.unmodifiableMap when you need to expose a read-only view of a map that is still owned by another object. For example, a class might keep a private mutable map and return an unmodifiable view to clients. This avoids copying the map while still preventing direct modification.

Common Pitfalls and Edge Cases

One subtle issue is that an immutable map only prevents structural modification; it does not make the values themselves immutable. If the map contains mutable objects, those objects can still be changed.

Map<String, List<String>> map = Map.of("key", new ArrayList<>()); map.get("key").add("value"); // This works

The map itself is unchanged, but the list it references is modified. To achieve deep immutability, you must also make the values immutable, such as using List.of instead of ArrayList.

Another edge case is the iteration order. Map.of and Map.copyOf do not guarantee any specific order. If your code relies on the order of entries, you should use a LinkedHashMap and wrap it with Collections.unmodifiableMap, or sort the entries before creating the map.

Finally, remember that Map.of and Map.copyOf reject null keys and values. If you need to allow null values, you must use Collections.unmodifiableMap with a HashMap that permits them. This is a deliberate tradeoff: the standard immutable maps prioritize safety and consistency over flexibility.

When an Immutable Map Is Not the Answer

If you need to modify the map frequently, consider using a mutable map with proper encapsulation, or a concurrent map like ConcurrentHashMap for thread safety. Immutable maps are best for data that is set once and then read many times. They are not suitable for dynamic data that changes over time.

For very large maps, the memory savings of Map.of may be less significant, and the lack of a resize mechanism means that the map is allocated with the exact number of entries. This can be an advantage, but it also means you cannot add entries later without creating a new map.

In summary, the Java standard library provides a clear set of tools for creating immutable maps. Understanding the difference between a copy and a view, and knowing when to use each, helps you write safer and more maintainable code.

java immutable map: Practical Usage and Code Examples | RYUSLOG DEV