Java Immutable and Unmodifiable Maps

In this tutorial, we’ll learn to use an unmodifiable and/or immutable Map in Java. Immutable classes help avoid lots of design challenges in multi-threaded applications.

1. Immutable vs. Unmodifiable Maps

Maps that do not support modification operations are referred to as unmodifiable. Unmodifiable maps are usually read-only views (wrappers) of other mutable maps. We can’t add, remove or clear them, but if we change the underlying map, this map’s view is also changed.

Map<String, String> mutableMap = new HashMap<>();
mutableMap.put("key1", "value1");

Map<String, String> unmodifiableMap
    = Collections.unmodifiableMap(mutableMap);

//Throws java.lang.UnsupportedOperationException
//unmodifiableMap.put("key2", "value2");

//Changes are visible in both maps
mutableMap.put("key2", "value2");
System.out.println(unmodifiableMap);  //{key1=value1, key2=value2}

Immutable maps guarantee that no change in the underlying map object will ever be visible. We can’t change immutable maps – they don’t wrap another map– they have their own elements. These are not views – these are data structures. Its contents will never change.

Map<String, String> immutableMap = Map.of("key1", "value1");

//throws java.lang.UnsupportedOperationException
immutableMap.put("key2", "value2");

2. Unmodifiable Maps

The Collectors.unmodifiableMap() was introduced in Java 8 as a part of Lambda expression changes. This static factory method takes a Map as the parameter and returns an unmodifiable view of type java.util.Collections$UnmodifiableMap.

Map<Integer, String> mutableMap = new HashMap<>();
//add few entries
		
Map<Integer, String> unmodifiableMap = Collections.unmodifiableMap(mutableMap);

Apache Commons Collections MapUtils class also provides a similar method.

Map<String, String> unmodifiable = MapUtils.unmodifiableMap(mutableMap);

3. Immutable Maps

3.1. Using Map.of() – Java 9

The Map.of() method was introduced in Java 9. Using this method, we can create an immutable map containing zero or up to 10 key-value pairs. The created maps are of type java.util.ImmutableCollections$MapN and would throw a NullPointerException if it encounters any null key or value.

var unmodifiableMap = Map.of(1, "Mumbai", 2, "Pune", 3, "Bangalore");
var emptyUnmodifiableMap = Map.of();

3.2. Using Guava’s ImmutableMap

Since Guava is an external library, it will have to be added to your classpath. If you’re using Maven, add the Guava dependency as follows:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

An ImmutableMap is an immutable Map implementation. Similar to other immutable classes, it rejects null values.

An ImmutableMap can be created in the following ways:

  • using the copyOf method
  • using the of method
  • using a Builder

The ImmutableMap.copyOf() takes a Map as an input parameter and creates an immutable map containing similar entries as the input map.

Map<Integer, String> mutableMap = new HashMap<>();
mutableMap.put(1, "Mumbai");
mutableMap.put(2, "Pune");
mutableMap.put(3, "Bangalore");

var immutableMap = ImmutableMap.copyOf(mutableMap);

The ImmutableMap.of() is similar to Map.of() except that it returns an immutable Map either empty or with a maximum of 10 key-value pairs. It returns an instance of type com.google.common.collect.RegularImmutableMap.

var immutableMap = ImmutableMap.of(1, "Mumbai", 2, "Pune", 3, "Bangalore");

var emptyImmutableMap  = ImmutableMap.of();

The ImmutableMap.builder() returns a builder that helps create an immutable Map. Using the builder, we can add additional entries to the immutable map which are not present in the original underlying map.

Map<Integer, String> mutableMap = new HashMap<>();
mutableMap.put(1, "Mumbai");
mutableMap.put(2, "Pune");
mutableMap.put(3, "Bangalore");

var immutableMap = ImmutableMap.builder()
	.putAll(mutableMap)
	.put(4, "Delhi")
	.build();

4. Performance and Efficiency

Unmodifiable maps return a read-only view of the original map. It will be a thin proxy over the original map. Unmodifiable maps are much faster and more memory efficient than returning a copy of the map.

However, modifications to the original map would still be reflected in the unmodifiable maps. The returned map is only truly immutable if nobody holds a reference to the original map.

Immutable maps, on the other hand, create an efficient copy of the original Map. When we don’t expect to modify the map or expect a map to remain constant, it’s a good practice to defensively copy it into an immutable map. It gives a guarantee that once it is created, no modifications can happen to the immutable map, even if the underlying map changes.

Creating defensive copies may be slightly expensive. So if we have a performance-critical application, we may want to go with unmodifiable maps. However, if we want to make sure the map remains immutable and no modifications to the underlying map create inconsistencies in the application, especially in a multi-threaded environment, we may want to choose an immutable map.

5. Conclusion

This Java tutorial explored various ways to create immutable and unmodifiable maps. It is recommended to use the solution available in the latest Java version we are using.

Happy Learning !!

Leave a Reply

0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions, and frequently asked interview questions.