Skip to content
Java collections 8 min read

Map Interface

The Map interface is your go-to tool whenever you need to associate keys with values — think dictionaries, phone books, or configuration tables. Unlike the rest of the collections framework, Map does not extend Collection; it lives in its own corner of java.util and has its own rules.

Map interface hierarchy tree

What Is the Map Interface?

A Map<K, V> stores entries, where each entry is a unique key mapped to a value. Keys must be unique; values do not have to be. If you put a second value under an existing key, the old value is replaced.

import java.util.HashMap;
import java.util.Map;

public class MapBasics {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();

        scores.put("Alice", 95);
        scores.put("Bob", 87);
        scores.put("Carol", 92);

        System.out.println("Bob's score: " + scores.get("Bob"));
        System.out.println("Size: " + scores.size());
        System.out.println(scores);
    }
}

Output:

Bob's score: 87
Size: 3
{Alice=95, Bob=87, Carol=92}

Note: Map is in java.util. Import java.util.Map plus the specific implementation you plan to use (HashMap, TreeMap, etc.).

The Map Hierarchy

Map is the root interface. Java ships several ready-to-use implementations, each optimized for a different scenario:

Map<K, V>
  ├── HashMap          — fast, unordered
  ├── LinkedHashMap    — insertion-ordered
  ├── TreeMap          — sorted by key
  ├── Hashtable        — legacy, synchronized
  └── EnumMap          — keys must be an enum type
ImplementationOrdered?Null keysNull valuesThread-safe?Performance
HashMapNo1 allowedYesNoO(1) avg
LinkedHashMapInsertion order1 allowedYesNoO(1) avg
TreeMapSorted (natural / Comparator)NoYesNoO(log n)
HashtableNoNoNoYes (legacy)O(1) avg

For thread-safe access in modern code, prefer ConcurrentHashMap from Concurrent Collections over the old Hashtable.

Core Methods

MethodDescription
put(K key, V value)Inserts or replaces the mapping; returns the old value (or null)
get(Object key)Returns the value for the key, or null if absent
getOrDefault(Object key, V def)Returns the value, or def if the key is missing
containsKey(Object key)Returns true if the key exists
containsValue(Object value)Returns true if the value exists (O(n) scan)
remove(Object key)Removes the mapping; returns the old value
remove(Object key, Object value)Removes only if the key maps exactly to that value
size()Number of key-value pairs
isEmpty()Returns true if the map has no entries
clear()Removes all entries
putIfAbsent(K key, V value)Puts only if the key is not already mapped
replace(K key, V value)Replaces only if the key already exists
keySet()Returns a Set<K> of all keys
values()Returns a Collection<V> of all values
entrySet()Returns a Set<Map.Entry<K,V>> — the best way to iterate

Working With the Methods

Putting and Getting Values

import java.util.HashMap;
import java.util.Map;

public class PutGet {
    public static void main(String[] args) {
        Map<String, String> capitals = new HashMap<>();

        capitals.put("France", "Paris");
        capitals.put("Japan", "Tokyo");
        capitals.put("India", "New Delhi");

        // get — returns null if key absent
        System.out.println(capitals.get("Japan"));           // Tokyo
        System.out.println(capitals.get("Brazil"));          // null

        // getOrDefault — safe fallback
        System.out.println(capitals.getOrDefault("Brazil", "Unknown")); // Unknown

        // put replaces existing
        String old = capitals.put("Japan", "Kyoto");
        System.out.println("Old value: " + old);             // Tokyo
        System.out.println("New value: " + capitals.get("Japan")); // Kyoto
    }
}

Output:

Tokyo
null
Unknown
Old value: Tokyo
New value: Kyoto

Checking for Keys and Values

import java.util.*;

public class ContainsDemo {
    public static void main(String[] args) {
        Map<String, Integer> inventory = new HashMap<>();
        inventory.put("apples", 50);
        inventory.put("bananas", 30);

        System.out.println(inventory.containsKey("apples"));   // true
        System.out.println(inventory.containsKey("grapes"));   // false
        System.out.println(inventory.containsValue(30));       // true
    }
}

Tip: containsKey is O(1) for HashMap and LinkedHashMap. containsValue always scans all values — O(n) — regardless of implementation.

Iterating Over a Map

The three common iteration patterns:

import java.util.*;

public class IterateMap {
    public static void main(String[] args) {
        Map<String, Integer> ages = new LinkedHashMap<>();
        ages.put("Alice", 30);
        ages.put("Bob", 25);
        ages.put("Carol", 28);

        // 1. entrySet — key + value together (most efficient)
        for (Map.Entry<String, Integer> entry : ages.entrySet()) {
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }

        // 2. keySet — when you only need keys
        for (String name : ages.keySet()) {
            System.out.println(name);
        }

        // 3. forEach (Java 8+) — cleanest
        ages.forEach((name, age) -> System.out.println(name + " is " + age));
    }
}

Output:

Alice -> 30
Bob -> 25
Carol -> 28
Alice
Bob
Carol
Alice is 30
Bob is 25
Carol is 28

Tip: Prefer entrySet() over calling get(key) inside a keySet() loop. Getting both key and value from a single Entry object is faster than two separate lookups.

putIfAbsent and computeIfAbsent

These two methods are perfect for building frequency maps, grouping results, and initializing defaults without verbose null-checks.

import java.util.*;

public class ComputeDemo {
    public static void main(String[] args) {
        String[] words = {"apple", "banana", "apple", "cherry", "banana", "apple"};

        Map<String, Integer> freq = new HashMap<>();
        for (String word : words) {
            // merge: if key absent, set to 1; else apply the function
            freq.merge(word, 1, Integer::sum);
        }
        System.out.println(freq);

        // computeIfAbsent: initialize a list only when needed
        Map<String, List<String>> groups = new HashMap<>();
        groups.computeIfAbsent("fruits", k -> new ArrayList<>()).add("mango");
        groups.computeIfAbsent("fruits", k -> new ArrayList<>()).add("kiwi");
        System.out.println(groups);
    }
}

Output:

{banana=2, cherry=1, apple=3}
{fruits=[mango, kiwi]}

Removing Entries

import java.util.*;

public class RemoveDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("a", 1);
        map.put("b", 2);
        map.put("c", 3);

        // Remove by key
        map.remove("a");

        // Conditional remove — only removes if key maps to exactly this value
        boolean removed = map.remove("b", 99);  // false, value is 2 not 99
        System.out.println("Conditional remove: " + removed);

        System.out.println(map);
    }
}

Output:

Conditional remove: false
{b=2, c=3}

Creating Immutable Maps (Java 9+)

import java.util.Map;

public class ImmutableMap {
    public static void main(String[] args) {
        // Up to 10 entries — factory method
        Map<String, Integer> config = Map.of(
            "timeout", 30,
            "retries", 3,
            "maxConnections", 100
        );

        System.out.println(config.get("timeout")); // 30
        // config.put("newKey", 5); // throws UnsupportedOperationException
    }
}

Note: Map.of() introduced in Java 9 does not allow null keys or values, and it does not guarantee iteration order. For more than 10 entries, use Map.ofEntries(Map.entry(k, v), ...).

Choosing the Right Implementation

import java.util.*;

public class MapChoice {
    public static void main(String[] args) {
        // HashMap — fastest, no order guarantee
        Map<String, Integer> hashMap = new HashMap<>();

        // LinkedHashMap — remembers insertion order
        Map<String, Integer> linked = new LinkedHashMap<>();

        // TreeMap — keys sorted alphabetically (natural order)
        Map<String, Integer> tree = new TreeMap<>();

        for (Map<String, Integer> m : List.of(hashMap, linked, tree)) {
            m.put("banana", 2);
            m.put("apple", 1);
            m.put("cherry", 3);
            System.out.println(m.getClass().getSimpleName() + ": " + m);
        }
    }
}

Output:

HashMap: {banana=2, cherry=3, apple=1}
LinkedHashMap: {banana=2, apple=1, cherry=3}
TreeMap: {apple=1, banana=2, cherry=3}

You can also supply a custom sort to TreeMap using a Comparator:

Map<String, Integer> reverseOrder = new TreeMap<>(Comparator.reverseOrder());
reverseOrder.put("apple", 1);
reverseOrder.put("banana", 2);
reverseOrder.put("cherry", 3);
System.out.println(reverseOrder); // {cherry=3, banana=2, apple=1}

Under the Hood

HashMap Internals

A HashMap internally uses an array of “buckets”. When you call put(key, value):

  1. Java calls key.hashCode() to compute a hash.
  2. The hash is spread across the bucket array using a bit-manipulation formula.
  3. The entry is stored in that bucket. If two keys land in the same bucket (a collision), they are chained together — as a linked list in older versions, and as a balanced red-black tree when the chain grows beyond 8 nodes (since Java 8).

Retrieval (get) follows the same hash path, then checks equals() to find the exact key within the bucket.

This is why two rules matter for any class you use as a Map key:

  • If you override equals(), you must override hashCode() consistently.
  • Equal objects must produce the same hash code, or get() will silently fail to find entries you just put.

See Working of HashMap for a deep visual walkthrough of buckets, load factor, and rehashing.

TreeMap Internals

TreeMap stores entries in a red-black tree — a self-balancing binary search tree. Every put, get, and remove is O(log n). In exchange, you get sorted keys for free, plus navigation methods like firstKey(), lastKey(), headMap(), and tailMap() that HashMap cannot offer.

The Map.Entry<K, V> Interface

When you call entrySet(), you get a live Set of Map.Entry objects. Each Entry holds a reference to the real key and value inside the map. This set is backed by the map — iterating it is as efficient as possible, and changes to the map are reflected immediately.

Warning: Do not modify a map’s structure (add or remove entries) while iterating over its entrySet(), keySet(), or values(). Doing so throws ConcurrentModificationException. Use entry.setValue() to change a value in-place, or collect changes and apply them after the loop.

Memory Layout

A HashMap with default settings starts with 16 buckets and a load factor of 0.75. When 75% of the buckets are occupied, it rehashes — creates a new array double the size and redistributes all entries. Rehashing is an O(n) operation. If you know in advance that your map will hold n entries, construct it with new HashMap<>(n * 2) to avoid mid-flight rehashing.

Last updated June 13, 2026
Was this helpful?