Map Interface

Su Suriya Ravichandran Updated 14 Nov 2026
6 min read ·Lesson 83 of 133

java.util.Map is the interface for objects that associate keys with values. Each key appears at most once and maps to exactly one value. Maps answer the question "given this key, what is the value?", which is why they underpin caches, configuration, indexes, counters, and lookup tables throughout Java applications. HashMap, LinkedHashMap, TreeMap, EnumMap, ConcurrentHashMap, and the immutable maps from Map.of all implement it.

Map Is Not a Collection

Map sits in its own hierarchy, separate from Collection:

Map
 ├── HashMap
 │     └── LinkedHashMap        (SequencedMap, Java 21)
 ├── SortedMap
 │     └── NavigableMap
 │           └── TreeMap
 ├── EnumMap
 ├── ConcurrentMap
 │     └── ConcurrentHashMap, ConcurrentSkipListMap
 └── Hashtable                   (legacy)

A map holds pairs, so it does not have add(e) or a plain iterator. Instead, it provides three collection views that connect it to the rest of the framework:

ViewTypeContents
keySet()Set<K>The keys (a set, since keys are unique)
values()Collection<V>The values (may contain duplicates)
entrySet()Set<Map.Entry<K, V>>The key-value pairs

The views are backed by the map: removing from a view removes from the map, and they reflect later changes to the map.

The Map Contract

RuleMeaning
Unique keysput with an existing key replaces its value
One value per keyTo associate several values with a key, make the value a collection
Key equalityHash-based maps use equals/hashCode; sorted maps use compareTo/Comparator
Map equalityTwo maps are equal if they contain the same key-value pairs, regardless of class or order
Map<String, Integer> a = new HashMap<>(Map.of("a", 1, "b", 2));
Map<String, Integer> b = new TreeMap<>(Map.of("b", 2, "a", 1));
System.out.println(a.equals(b));    // true

Core Operations

MethodPurpose
put(k, v)Insert or replace; returns the previous value or null
get(k)Value for k, or null
getOrDefault(k, d)Value for k, or d
containsKey(k), containsValue(v)Membership tests (the second scans all values)
remove(k)Remove the entry; returns the old value or null
remove(k, v)Remove only if k currently maps to v
putIfAbsent(k, v)Insert only if absent
replace(k, v), replace(k, old, new)Replace only if present (and, in the second form, only if equal to old)
merge, compute, computeIfAbsent, computeIfPresentUpdate values with a function
putAll(m)Copy all entries from another map
forEach((k, v) -> ...), replaceAll((k, v) -> newV)Act on or transform every entry
size(), isEmpty(), clear()Bookkeeping

The HashMap lesson covers these methods in depth. They are defined by the Map interface, so they work identically on every implementation.

Map.Entry

Map.Entry<K, V> represents one key-value pair. You meet it when iterating entrySet():

Map<String, Integer> stock = new TreeMap<>(Map.of("pen", 120, "bag", 0, "ink", 35));

for (Map.Entry<String, Integer> e : stock.entrySet()) {
    System.out.println(e.getKey() + " -> " + e.getValue());
}

Entries can be created directly with Map.entry(k, v) (immutable), and sorted with ready-made comparators:

List<Map.Entry<String, Integer>> entries = new ArrayList<>(stock.entrySet());
entries.sort(Map.Entry.comparingByValue(Comparator.reverseOrder()));   // highest stock first

Map.Entry.comparingByKey() and comparingByValue() accept an optional comparator for the key or value.

Programming to the Map Interface

Map<String, Integer> wordCounts = new HashMap<>();

public Map<String, List<Order>> groupByCustomer(List<Order> orders) { ... }

Declare variables, parameters, and return types as Map so the implementation can change. Use SortedMap or NavigableMap as the declared type when code depends on key order or methods like floorKey, and SequencedMap (Java 21) when it depends on a defined encounter order:

SequencedMap<String, Integer> steps = new LinkedHashMap<>();
steps.put("x", 1);
steps.put("y", 2);
steps.put("z", 3);
System.out.println(steps.firstEntry());   // x=1
System.out.println(steps.lastEntry());    // z=3
System.out.println(steps.reversed());     // {z=3, y=2, x=1}

SequencedMap also provides pollFirstEntry, pollLastEntry, putFirst, and putLast.

Choosing an Implementation

ImplementationKey orderget / putNotes
HashMapNoneO(1) averageGeneral default
LinkedHashMapInsertion order (or access order)O(1) averagePredictable iteration; basis for LRU caches
TreeMapSorted by keyO(log n)Range and nearest-key queries
EnumMapEnum declaration orderO(1)Enum keys only; array-backed, very compact
Map.of / Map.copyOfUnspecifiedO(1)Immutable; no null keys or values
ConcurrentHashMapNoneO(1) averageThread-safe without locking the whole map
HashtableNoneO(1) averageLegacy synchronized map; avoid in new code

Immutable Maps

Map<String, Integer> limits = Map.of("free", 1, "pro", 5, "team", 25);

Map<String, String> codes = Map.ofEntries(
    Map.entry("IN", "India"),
    Map.entry("JP", "Japan"),
    Map.entry("US", "United States")
);

Map<String, Integer> snapshot = Map.copyOf(liveMap);

These maps are unmodifiable, reject null, and have no defined iteration order. Map.of throws IllegalArgumentException for duplicate keys. Collections.unmodifiableMap(map) instead returns a read-only view that tracks the underlying map.

Common Map Patterns

Defaults overridden by specific values

Layer settings by copying defaults and then applying overrides with putAll:

Map<String, String> defaults = Map.of("theme", "light", "lang", "en", "pageSize", "20");
Map<String, String> user = Map.of("theme", "dark");

Map<String, String> effective = new TreeMap<>(defaults);
effective.putAll(user);
System.out.println(effective);   // {lang=en, pageSize=20, theme=dark}

One key, many values (multimap)

Map<String, List<String>> coursesByStudent = new HashMap<>();
coursesByStudent.computeIfAbsent("ada", k -> new ArrayList<>()).add("Java");
coursesByStudent.computeIfAbsent("ada", k -> new ArrayList<>()).add("SQL");
// {ada=[Java, SQL]}

Counting

Map<Character, Integer> freq = new TreeMap<>();
for (char c : "banana".toCharArray()) {
    freq.merge(c, 1, Integer::sum);
}
System.out.println(freq);   // {a=3, b=1, n=2}

Inverting a map

Map<String, String> codeToCountry = Map.of("IN", "India", "JP", "Japan");
Map<String, String> countryToCode = new HashMap<>();
codeToCountry.forEach((code, country) -> countryToCode.put(country, code));

Inversion is safe only if the values are unique; otherwise later entries overwrite earlier ones.

Requirements on Keys

  • Hash-based maps need keys with consistent equals and hashCode.
  • Sorted maps need keys that are Comparable or a Comparator; keys comparing as 0 are the same key.
  • Keys should be immutable. A key whose hash code or ordering changes after insertion becomes unreachable.

String, wrapper types, enums, records, and java.time types are all good keys. Values have no such requirements.

Practical Example: An LRU Cache

A least-recently-used (LRU) cache keeps a fixed number of entries and, when full, evicts the one that has gone unused the longest. LinkedHashMap supports this directly: it can keep entries in access order, and it calls a hook method after each insertion that can request removal of the oldest entry.

import java.util.*;

public class LruCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;

    public LruCache(int capacity) {
        super(16, 0.75f, true);            // true = access order instead of insertion order
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;          // evict the least recently used entry
    }

    public static void main(String[] args) {
        LruCache<String, String> pages = new LruCache<>(3);
        pages.put("/home", "<home html>");
        pages.put("/java", "<java html>");
        pages.put("/docker", "<docker html>");
        System.out.println("Cached:  " + pages.keySet());

        pages.get("/home");                // touch /home: now most recently used
        pages.put("/django", "<django html>");
        System.out.println("After adding /django: " + pages.keySet());

        System.out.println("Hit /java?   " + pages.containsKey("/java"));
        System.out.println("Hit /docker? " + pages.containsKey("/docker"));
    }
}

Output:

Cached:  [/home, /java, /docker]
After adding /django: [/docker, /home, /django]
Hit /java?   false
Hit /docker? true

The constructor's third argument, true, switches LinkedHashMap from insertion order to access order: every get or put of a key moves it to the end of the iteration order, so the first entry is always the least recently used. After each put, LinkedHashMap calls removeEldestEntry; returning true removes that first entry. When /django is added, /home has just been read, so /java is the oldest and is evicted. containsKey does not count as an access, so checking for a key does not change the order. Because LruCache is a Map, any code written against the Map interface can use it without knowing about eviction. (This class is not thread-safe; a cache shared between threads needs synchronization or a dedicated caching library.)

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Map Interface and get answers drawn from it.

Signed-in readers only.