Skip to main content

Command Palette

Search for a command to run...

Java 9 Immutable Collections Example

Updated
Java 9 Immutable Collections Example
Y

Tech Lead & Architect | 13+ Years in Cloud, Backend, and AI - Experienced software engineer with expertise in Java, Spring Boot, Microservices, Angular, React, Kafka, DevOps, Python, PySpark, Databricks, and Generative AI. Certified in TOGAF, AWS, and Google Cloud. Passionate about building scalable, secure, and high-performance systems. Enthusiast in Data Engineering & Agentic AI. Author of 1,200+ technical articles sharing insights across diverse tech stacks.

Date: 2017-07-05

Java 9 and the Revolution of Immutable Collections

Before Java 9, creating immutable collections in Java was a cumbersome process. While it was possible to achieve immutability using methods like Collections.unmodifiableCollection(), the syntax was verbose and didn't offer the elegance desired for such a common task. This meant developers often wrote more code than necessary simply to ensure a collection could not be altered after creation. This complexity increased the risk of errors and made code harder to read and maintain. The introduction of immutable collection factory methods in Java 9 addressed this issue, significantly simplifying the creation of small, unmodifiable collections.

The core improvement lies in the addition of convenient static factory methods. These methods provide a concise, one-line approach to initializing immutable lists, sets, and maps. Prior to Java 9, the process involved several steps and often required understanding and using specific utility methods. This change directly tackles the problem of unnecessary code bloat, leading to more readable and efficient Java code.

Java's new factory methods adhere to a simple principle: they create collections that cannot be modified after initialization. This immutability prevents accidental changes, improving the reliability and predictability of your programs. This is especially crucial in concurrent programming where multiple threads might access the same data structure. With an immutable collection, you eliminate the risk of race conditions and data corruption caused by concurrent modification.

The fundamental change introduced a new approach to creating immutable collections. The factory methods provide a clean and straightforward syntax for creating these collections. For example, to create an immutable list containing the integers 1, 2, 3, and 4, one would use List.of(1, 2, 3, 4). This is a significant improvement over the more complex approaches needed in earlier Java versions. Similarly, Set.of() and Map.of() create immutable sets and maps, respectively. These methods greatly reduce the amount of boilerplate code needed to generate unmodifiable collections.

However, these seemingly simple methods have limitations. Attempts to add, remove, or modify elements in these immutable collections will result in an UnsupportedOperationException. This exception is thrown at runtime, preventing the modification and highlighting the immutability enforced by the factory methods. The runtime exception is a key feature that protects the integrity of the collection, immediately signaling any attempt to break its unmodifiable nature.

Furthermore, null values are not permitted within these collections. Attempting to create an immutable collection with a null element will lead to a NullPointerException. This behavior is consistent with the general handling of null values in Java and further emphasizes the robustness of these factory methods.

The limitations of List.of(), Set.of(), and Map.of() are directly linked to their intended use. These methods are most effective for creating small, fixed-size collections. For larger collections or those needing dynamic modifications, the older methods of creating collections and then making them immutable using Collections.unmodifiableCollection() remain an option. The new factory methods are a streamlined solution targeted at situations where the size and contents of a collection are known at the time of creation and no subsequent changes are anticipated.

For maps containing more than ten key-value pairs, the Map.ofEntries() method provides an alternative. This method utilizes an approach that allows for the creation of larger immutable maps while maintaining the benefits of immutability. The ofEntries() method accepts a variable number of key-value pairs, effectively scaling the ability to create larger immutable maps.

It's important to note that although the factory methods create immutable collections, their use doesn't inherently restrict the underlying data types. You can use these methods with any type as long as it is consistent with the collection type being created (e.g., you can create an immutable list of Strings). If you need a mutable version of an immutable collection that has been created using these methods, you can easily create a copy in a mutable collection using new ArrayList<>(List.of(...)), new HashSet<>(Set.of(...)), or new HashMap<>(Map.of(...)). This allows for flexibility while still maintaining the integrity and safety of the original immutable collection.

The danger of using these methods lies in their deceptive simplicity. The methods themselves don't explicitly indicate that they return immutable collections. Furthermore, the continued presence of add() and put() methods (which throw exceptions when called on immutable collections) can lead to unexpected runtime errors if a developer mistakenly assumes that they are working with a mutable collection. This ambiguity highlights the critical need for developers to understand the nuances of immutable collections and to carefully consider the implications when utilizing these convenient factory methods. Clear documentation and a deep understanding of the intended behavior are paramount.

In conclusion, Java 9's immutable collection factory methods represent a significant improvement in the language's capabilities. They provide a concise and efficient way to create small, unmodifiable collections. However, it's crucial to be aware of the limitations and potential for runtime errors caused by misunderstanding their immutable nature. When used correctly, these methods enhance the readability, maintainability, and safety of Java code. The concise syntax provides a modern solution for a common programming problem, leading to cleaner and more robust applications. By carefully understanding and utilizing these new methods, developers can improve the overall quality and reliability of their Java projects.

Read more

More from this blog

The Engineering Orbit

1174 posts

The Engineering Orbit shares expert insights, tutorials, and articles on the latest in engineering and tech to empower professionals and enthusiasts in their journey towards innovation.