Java Map Interface Explained: HashMap, LinkedHashMap, and TreeMap

Learn the Java Map interface with HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap, key-value lookup, ordering, performance, and real-world examples.

Last reviewed: August 2026

·Java 8Java 11Java 17Java 21
✅
Quick Understanding: A Java Map stores key-value pairs. Use HashMap for general fast lookup, LinkedHashMap when insertion order matters, TreeMap for sorted keys, and ConcurrentHashMap for concurrent access. Maps are common in caching, grouping, configuration, and lookup tables.

🔍 What Is It?

Map Interface

java.util.Map (does NOT extend Collection):

  • →put(K key, V value) — add or replace entry
  • →get(Object key) — retrieve by key (returns null if missing)
  • →getOrDefault(Object key, V defaultValue) — safe get
  • →containsKey(Object key) — O(1) for HashMap
  • →remove(Object key) — remove entry
  • →keySet() — Set of all keys
  • →values() — Collection of all values
  • →entrySet() — Set<Map.Entry<K,V>> for iterating pairs
  • →putIfAbsent(K key, V value) — only puts if key not present
  • →computeIfAbsent(K key, Function) — compute value if absent
  • →merge(K key, V value, BiFunction) — merge with existing value

Key Implementations:

  • →HashMap — hash table; O(1) average; no ordering
  • →LinkedHashMap — HashMap + insertion order; O(1); useful for LRU cache
  • →TreeMap — Red-Black tree; O(log n); keys always sorted
  • →ConcurrentHashMap — thread-safe HashMap; fine-grained locking
  • →EnumMap — specialized for enum keys; array-backed; very fast
  • →Map.of() — immutable map (Java 9+); max 10 entries
  • →Map.copyOf() — immutable copy of existing map

🎯 Why Do We Need It?

Maps are the most versatile data structure in Java:

  • →Fast lookup — O(1) find by key (vs O(n) for List)
  • →Grouping — group orders by customer, products by category
  • →Caching — store computed results keyed by input
  • →Configuration — property name to value
  • →Counting — word frequency, event counts
  • →Index — build an in-memory index for fast search

🏗️ What Happens in Real Projects?

Maps appear in virtually every Java service:

// Cache / lookup table
Map<String, User> userCache = new HashMap<>();
User user = userCache.computeIfAbsent(userId, id -> userRepo.findById(id));

// Grouping (replaced by Collectors.groupingBy in streams)
Map<String, List<Order>> ordersByCustomer = orders.stream()
    .collect(Collectors.groupingBy(Order::getCustomerId));

// Counting
Map<String, Long> countByStatus = orders.stream()
    .collect(Collectors.groupingBy(
        o -> o.getStatus().name(),
        Collectors.counting()
    ));

⚙️ How It Works

HashMap Internals:

  • →Array of "buckets" (linked lists or trees)
  • →put(key, value): compute hashCode, find bucket, check for existing key with equals(), store
  • →get(key): compute hashCode, find bucket, find entry with equals()
  • →Java 8+: buckets with 8+ entries convert to balanced trees (O(log n) worst case)
  • →Default initial capacity: 16; load factor: 0.75 (resize when 75% full)
  • →Resizing doubles the array and rehashes all entries — O(n) operation

Good hash function distributes keys evenly across buckets. Poor hash functions (many collisions) degrade HashMap to O(n).

💻 Example

Map Patterns Every Developer Uses — The Map operations and patterns you will use in every enterprise project.

java
// Creating maps
Map<String, Integer> scores = new HashMap<>();
Map<String, String> config = Map.of("host", "localhost", "port", "8080");  // immutable

// Basic operations
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Alice", 98);  // replaces 95 with 98
int aliceScore = scores.get("Alice");  // 98
int missing = scores.getOrDefault("Charlie", 0);  // 0 (safe)
scores.remove("Bob");

// Modern Map methods (Java 8+)
// putIfAbsent — only adds if key not present
scores.putIfAbsent("Dave", 75);

// computeIfAbsent — compute and store if absent (great for caching)
Map<String, List<String>> groups = new HashMap<>();
groups.computeIfAbsent("admin", k -> new ArrayList<>()).add("alice");
groups.computeIfAbsent("admin", k -> new ArrayList<>()).add("bob");
// groups = {admin: [alice, bob]}

// merge — combine with existing value
Map<String, Integer> wordCount = new HashMap<>();
String[] words = {"apple", "banana", "apple", "cherry", "banana", "apple"};
for (String word : words) {
    wordCount.merge(word, 1, Integer::sum);
    // if absent: put(word, 1); if present: put(word, existing + 1)
}
// {apple=3, banana=2, cherry=1}

// Iteration
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}
scores.forEach((k, v) -> System.out.println(k + ": " + v));

🤔 Common Confusions

❓ What happens when HashMap has many collisions?

If many keys hash to the same bucket, performance degrades. Before Java 8, buckets were linked lists — worst case O(n). Java 8+ converts buckets with 8+ entries to balanced trees — worst case O(log n). The real fix is a good hashCode() implementation that distributes keys evenly. String and Integer have excellent hash functions; custom objects need careful implementation.

❓ What is the difference between HashMap and Hashtable?

Hashtable is the legacy synchronized HashMap from Java 1.0. It is slower (all methods synchronized), does not allow null keys or values, and is considered obsolete. Use HashMap for single-threaded code and ConcurrentHashMap for thread-safe concurrent access. Never use Hashtable in new code.

❓ Why does Map not extend Collection?

Map represents a mapping (key to value) — it is fundamentally different from a Collection (sequence of elements). A Map entry is a key-value pair, not a single element. Forcing Map into the Collection hierarchy would require deciding: does size() return key count, value count, or entry count? The designers kept them separate for clarity.

🚨 Common Production Issues

HashMap in concurrent code — HashMap is not thread-safe. Concurrent modification can cause infinite loops (Java 7) or data corruption. Use ConcurrentHashMap for concurrent access.

Memory leak with computeIfAbsent for caching — unbounded maps grow forever. Use a cache library (Caffeine, Guava Cache) with eviction policies instead.

HashMap initial capacity — if you know you will store 10,000 entries, initialize with new HashMap<>(16384) (next power of 2 above 10000/0.75) to avoid resizing.

✅ Best Practices

Use HashMap as default; LinkedHashMap when order matters; TreeMap when sorted iteration is needed.

Use getOrDefault() instead of get() + null check.

Use computeIfAbsent() for lazy initialization patterns (e.g., building a map of lists).

Use merge() for counting and accumulation patterns.

Use ConcurrentHashMap for thread-safe maps — never synchronize a HashMap manually.

For enum keys, use EnumMap — it is faster than HashMap for enum types.

🏛️ Architect's Note

Senior Architect's Perspective

Maps are the foundation of most caching strategies. However, in-memory HashMaps are not suitable for distributed caching (multiple instances do not share state). For production caching, use Redis (via Spring Cache), Caffeine (local cache with eviction), or Hazelcast (distributed). The Map interface is the same — swap the implementation without changing business code.

💬 Frequently Asked Questions

What is ConcurrentHashMap and how is it different from synchronized HashMap?

ConcurrentHashMap uses fine-grained locking — it divides the map into segments (Java 7) or uses CAS operations (Java 8+). Multiple threads can read and write concurrently to different segments without blocking each other. Collections.synchronizedMap(new HashMap<>()) uses a single lock for the entire map — only one thread can access it at a time. ConcurrentHashMap is much faster under concurrent access.

How do I sort a Map by value?

Maps are sorted by key (TreeMap) or unsorted (HashMap). To sort by value: map.entrySet().stream().sorted(Map.Entry.comparingByValue()).forEach(e -> ...). To get a sorted map by value, collect to a LinkedHashMap: map.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)).

🎯 Key Takeaways

1HashMap: O(1) average for get/put/remove; no ordering guarantee
2LinkedHashMap: O(1) + insertion order; use for LRU cache
3TreeMap: O(log n) + sorted keys; use when sorted iteration is needed
4ConcurrentHashMap: thread-safe HashMap; use in concurrent code
5Use computeIfAbsent() for lazy initialization; merge() for counting
6Keys must have correct equals() and hashCode()

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.