Java 9 – Factory Methods for Immutable List: List.of() Explained
Java 9 introduced List.of(), a convenient static factory method for creating immutable lists. Before Java 9, creating an unmodifiable list took three steps: create a mutable list, add elements, then call Collections.unmodifiableList(). With List.of(), you can do it in a single line.
This tutorial covers the List.of() factory method for both String elements and custom Java beans (POJOs), and explains the immutability rules that apply.
The Old Way vs the Java 9 Way
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
// --- Before Java 9 ---
// Option 1: Arrays.asList() – fixed size but NOT fully immutable (set() still works)
List<String> old1 = Arrays.asList("Apple", "Mango", "Banana");
old1.set(0, "Orange"); // This WORKS with Arrays.asList()!
// Option 2: Collections.unmodifiableList() – truly immutable but verbose
List<String> old2 = Collections.unmodifiableList(Arrays.asList("Apple", "Mango"));
// --- Java 9 ---
// List.of() – concise, truly immutable, rejects null elements
List<String> fruits = List.of("Apple", "Mango", "Banana", "Grapes", "Orange");
Example 1: List.of() with Strings
package com.java9r;
import java.util.List;
import java.util.stream.Collectors;
/**
* Java 9 – Immutable List.of() with String elements.
*/
public class Java9ImmutableListString {
public static void main(String[] args) {
// Create immutable list of product names
var products = List.of(
"Laptop", "Smartphone", "Tablet", "Monitor",
"Keyboard", "Mouse", "Headphones", "Webcam"
);
System.out.println("Products: " + products);
System.out.println("Size: " + products.size());
System.out.println("Index 2: " + products.get(2));
System.out.println("Contains 'Mouse': " + products.contains("Mouse"));
// Iterate using for-each
System.out.println("\nAll products:");
for (var product : products) {
System.out.println(" - " + product);
}
// Use Stream API to filter
System.out.println("\nProducts with 'a' in name:");
products.stream()
.filter(p -> p.toLowerCase().contains("a"))
.sorted()
.forEach(p -> System.out.println(" " + p));
// Convert to mutable list when you need to modify
System.out.println("\nAdding to mutable copy:");
var mutable = new java.util.ArrayList<>(products);
mutable.add("Printer");
System.out.println("Mutable list size: " + mutable.size());
// Immutability check
System.out.println("\nImmutability check:");
try {
products.add("Monitor");
} catch (UnsupportedOperationException e) {
System.out.println("add() throws: " + e.getClass().getSimpleName());
}
try {
products.set(0, "Desktop");
} catch (UnsupportedOperationException e) {
System.out.println("set() throws: " + e.getClass().getSimpleName());
}
try {
products.remove(0);
} catch (UnsupportedOperationException e) {
System.out.println("remove() throws: " + e.getClass().getSimpleName());
}
}
}
Expected Output
Products: [Laptop, Smartphone, Tablet, Monitor, Keyboard, Mouse, Headphones, Webcam]
Size: 8
Index 2: Tablet
Contains 'Mouse': true
All products:
- Laptop
- Smartphone
- Tablet
- Monitor
- Keyboard
- Mouse
- Headphones
- Webcam
Products with 'a' in name:
Headphones
Keyboard
Laptop
Smartphone
Tablet
Webcam
Adding to mutable copy:
Mutable list size: 9
Immutability check:
add() throws: UnsupportedOperationException
set() throws: UnsupportedOperationException
remove() throws: UnsupportedOperationException
Example 2: List.of() with Java Bean Objects
package com.java9r;
import java.util.List;
class Product {
private String id;
private String name;
private double price;
public Product(String id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
public String getId() { return id; }
public String getName() { return name; }
public double getPrice() { return price; }
@Override
public String toString() {
return String.format("Product{id='%s', name='%s', price=%.2f}", id, name, price);
}
}
/**
* Java 9 – Immutable List.of() with Bean objects.
*/
public class Java9ImmutableListBean {
public static void main(String[] args) {
// Create immutable list of Product beans
var catalog = List.of(
new Product("P001", "Laptop", 75000.00),
new Product("P002", "Smartphone", 25000.00),
new Product("P003", "Tablet", 35000.00),
new Product("P004", "Monitor", 15000.00),
new Product("P005", "Keyboard", 2500.00)
);
System.out.println("Product Catalog:");
catalog.forEach(System.out::println);
// The List is immutable – you cannot add/remove Products
// But the Product objects themselves ARE mutable (unless you make them immutable)
System.out.println("\nProducts priced above Rs. 20,000:");
catalog.stream()
.filter(p -> p.getPrice() > 20_000)
.sorted((a, b) -> Double.compare(b.getPrice(), a.getPrice())) // descending
.forEach(p ->
System.out.printf(" %-12s Rs. %,.0f%n", p.getName(), p.getPrice()));
System.out.println("\nCheapest product: " +
catalog.stream()
.min((a, b) -> Double.compare(a.getPrice(), b.getPrice()))
.map(Product::getName)
.orElse("none")
);
System.out.println("Total catalog value: Rs. " +
String.format("%,.0f",
catalog.stream().mapToDouble(Product::getPrice).sum()));
}
}
Expected Output
Product Catalog:
Product{id='P001', name='Laptop', price=75000.00}
Product{id='P002', name='Smartphone', price=25000.00}
Product{id='P003', name='Tablet', price=35000.00}
Product{id='P004', name='Monitor', price=15000.00}
Product{id='P005', name='Keyboard', price=2500.00}
Products priced above Rs. 20,000:
Laptop Rs. 75,000
Tablet Rs. 35,000
Smartphone Rs. 25,000
Cheapest product: Keyboard
Total catalog value: Rs. 1,52,500
List.of() vs Arrays.asList() vs Collections.unmodifiableList()
| Method | add/remove? | set()? | null? | Backed by array? |
|---|---|---|---|---|
| List.of() | No (throws) | No (throws) | No (throws NPE) | No |
| Arrays.asList() | No (throws) | Yes | Yes | Yes (reflects changes) |
| Collections.unmodifiableList() | No (throws) | No (throws) | Depends on backing list | Yes (view) |
Summary
List.of() in Java 9 is the cleanest way to create an immutable list. It is concise, rejects null elements, and prevents any modification. Use it for constants, configuration values, and any data that should not change after initialization. When you need a modifiable version, pass it to new ArrayList<>(). Unlike Arrays.asList(), List.of() is completely immutable — even the set() method throws an exception.
Comments