Q28 of 40 · Core Java
What is the difference between Map.of() and a HashMap with put() calls?
Short answer
Short answer: Map.of() (Java 9+) produces an unmodifiable map with no defined iteration order, null keys/values forbidden, and fixed at creation time. HashMap is mutable, allows null, and has undefined but stable-until-resize iteration order. Map.of() is for constants and test fixtures; HashMap is for dynamic state.
Detail
Map.of(k1, v1, k2, v2, ...) and its sibling Map.copyOf() and Map.ofEntries() create unmodifiable maps. Any attempt to put(), remove(), or clear() throws UnsupportedOperationException. null keys or values throw NullPointerException immediately. The iteration order is deliberately random across JVM runs (to prevent code from accidentally depending on insertion order). Maximum capacity with Map.of() is 10 entries; use Map.ofEntries(Map.entry(k,v), ...) for more.
HashMap is mutable, accepts null keys and values (one null key, any number of null values), and iteration order is undefined but consistent within a single run until the map is resized.
When to use each:
Map.of()— configuration constants, test fixture data that shouldn't change, default values, lookup tables known at compile time. Communicates intent: "this map is immutable by design".HashMap— maps built incrementally, maps modified during processing, maps that start empty and grow, maps that need null keys/values.
Mutation-safe defensive copies: when a method receives a map you don't control and needs an unmodifiable view, use Collections.unmodifiableMap(map) (a view — changes in the original propagate through) or Map.copyOf(map) (a true immutable snapshot that doesn't reflect later changes to the source). Choose based on whether you want a live view or a snapshot.
// EXAMPLE
MapOfVsHashMap.java
import java.util.HashMap;
import java.util.Map;
// Map.of() — unmodifiable, no nulls, iteration order undefined
Map<String, String> headers = Map.of(
"Content-Type", "application/json",
"Accept", "application/json",
"X-Api-Version","2"
);
// headers.put("X-Extra", "value"); // UnsupportedOperationException
// Map.of("key", null); // NullPointerException at creation
// HashMap — mutable, allows null, grows dynamically
Map<String, Integer> responseCodes = new HashMap<>();
responseCodes.put("login", 200);
responseCodes.put("notFound", 404);
responseCodes.put(null, -1); // null key allowed in HashMap
// Map.ofEntries for > 10 entries
Map<String, String> env = Map.ofEntries(
Map.entry("BASE_URL", "https://api.staging.com"),
Map.entry("TIMEOUT", "5000"),
Map.entry("RETRIES", "3"),
Map.entry("LOG_LEVEL", "INFO")
// ... up to any number of entries
);
// Snapshot (immutable copy) from a mutable map
Map<String, Integer> snapshot = Map.copyOf(responseCodes);
// snapshot is now independent — mutations to responseCodes don't affect it
// View (reflects mutations) — different behaviour!
Map<String, Integer> view = Collections.unmodifiableMap(responseCodes);
responseCodes.put("serverError", 500);
view.get("serverError"); // 500 — view reflects the change