HashMap

Su Suriya Ravichandran Updated 14 Nov 2026
8 min read ·Lesson 75 of 133

A HashMap stores key-value pairs. Each key maps to exactly one value, and you retrieve a value by its key rather than by a numeric position. Lookups, insertions, and removals take constant time on average, which makes HashMap one of the most heavily used classes in Java: caches, configuration settings, counting occurrences, indexing records by ID, and grouping data all rely on it.

Creating and Using a HashMap

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

Map<String, Integer> stock = new HashMap<>();
stock.put("pen", 120);
stock.put("notebook", 45);
stock.put("eraser", 80);

System.out.println(stock.get("notebook"));   // 45
System.out.println(stock.get("stapler"));    // null: no such key
System.out.println(stock.size());            // 3

The two type arguments are the key type and the value type: Map<String, Integer> maps strings to integers. As with other collections, declare the variable with the interface type Map and choose HashMap as the implementation.

Keys are unique, values are not

Putting a value for an existing key replaces the old value:

Map<String, Integer> stock = new HashMap<>();
stock.put("pen", 10);
Integer old = stock.put("pen", 15);          // returns the previous value: 10
System.out.println(stock.get("pen"));        // 15

Different keys can map to the same value. put returns the previous value for that key, or null if there was none.

Core Methods

MethodEffect
put(key, value)Associate value with key; returns the previous value or null
get(key)The value for key, or null if absent
getOrDefault(key, default)The value, or default if absent
containsKey(key)Whether the key is present
containsValue(value)Whether any key maps to this value (slow: scans all values)
remove(key)Remove the entry; returns the removed value or null
putIfAbsent(key, value)Put only if the key is absent; returns the existing value or null
size(), isEmpty(), clear()Count, emptiness, remove everything
keySet(), values(), entrySet()Views of the keys, the values, and the key-value pairs

get and null

get returns null both when the key is absent and when the key maps to null (HashMap allows one null key and any number of null values). Two consequences:

Map<String, Integer> stock = new HashMap<>();
int count = stock.get("ink");                // NullPointerException: unboxing null
int safe = stock.getOrDefault("ink", 0);     // 0

Use getOrDefault when absent keys have a sensible default, and containsKey when you must distinguish "absent" from "present with a null value." Avoiding null values altogether makes maps much easier to work with.

Iterating Over a Map

A map is not a Collection, but it provides three collection views:

Map<String, Integer> stock = new HashMap<>(Map.of("pen", 120, "notebook", 45, "eraser", 80));

for (String item : stock.keySet()) {
    System.out.println(item);
}

for (int qty : stock.values()) {
    System.out.println(qty);
}

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

stock.forEach((item, qty) -> System.out.println(item + ": " + qty));

When you need both keys and values, iterate over entrySet() (or use forEach). Iterating over keySet() and calling get for each key performs a second lookup per entry for no reason.

Order is not guaranteed

HashMap makes no promise about iteration order; it depends on the keys' hash codes and the table size, and can change as entries are added. Use:

  • LinkedHashMap to iterate in insertion order,
  • TreeMap to iterate in sorted key order (next lesson).

Modifying while iterating

Adding or removing entries directly while iterating over a view throws ConcurrentModificationException (usually). Use removeIf on a view, or the iterator's remove:

stock.values().removeIf(qty -> qty == 0);                   // remove out-of-stock items
stock.entrySet().removeIf(e -> e.getKey().startsWith("x"));

Updating values with put for keys already in the map is safe, since it does not change the structure.

Updating Values: merge, compute, and Friends

Common update patterns used to require several lines:

// Old style: count occurrences
Integer c = counts.get(word);
counts.put(word, c == null ? 1 : c + 1);

Since Java 8, single methods express these patterns directly:

Map<String, Integer> counts = new HashMap<>();
for (String word : "to be or not to be".split(" ")) {
    counts.merge(word, 1, Integer::sum);
}
System.out.println(counts.get("to"));   // 2
MethodBehavior
merge(key, value, fn)If absent, put value; otherwise put fn(oldValue, value). If fn returns null, the entry is removed.
computeIfAbsent(key, fn)If absent, compute a value with fn(key), store it, and return it; otherwise return the existing value
computeIfPresent(key, fn)If present, replace the value with fn(key, oldValue)
compute(key, fn)Always compute a new value from fn(key, oldValueOrNull)
replace(key, value)Replace only if the key is present

computeIfAbsent is the standard way to build a map of collections, for example grouping items:

Map<String, List<String>> byCity = new HashMap<>();
byCity.computeIfAbsent("Chennai", k -> new ArrayList<>()).add("Ada");
byCity.computeIfAbsent("Chennai", k -> new ArrayList<>()).add("Grace");
byCity.computeIfAbsent("Pune", k -> new ArrayList<>()).add("Alan");
// {Chennai=[Ada, Grace], Pune=[Alan]}  (order may vary)

The list is created only the first time a key appears; later calls return the existing list, and .add appends to it.

How HashMap Works

A HashMap holds an array of buckets. To store or find a key:

  1. It calls key.hashCode() and mixes the high bits into the low bits (h ^ (h >>> 16)) to spread values evenly.
  2. It reduces the result to a bucket index by keeping only as many low bits as the table size needs (the table size is always a power of two).
  3. It looks only in that bucket, using key.equals(...) to find the matching entry.
put("pen", 120)   with the default table of 16 buckets
  "pen".hashCode()        → 110873
  mix: 110873 ^ (110873 >>> 16) → 110872
  index: 110872 & 15      → bucket 8
  bucket 8: [("pen", 120)]

get("pen") → bucket 8 → compare keys with equals → 120

Because only one bucket is examined, the cost does not depend on how many entries the map holds. Keys that land in the same bucket form a short list; if a bucket becomes large, it is converted into a balanced tree so performance degrades gracefully. When the map is about 75% full (the default load factor), the table doubles in size and entries are redistributed, so the same key may then land in a different bucket.

For a map that will hold a known number of entries, HashMap.newHashMap(expectedSize) (Java 19+) creates a table large enough to avoid resizing.

Keys need correct equals and hashCode

Everything in the previous lesson about HashSet applies to map keys (a HashSet is in fact built on a HashMap):

  • Key classes must override both equals and hashCode, consistently. String, wrapper types, enums, LocalDate, and records already do.
  • Keys must not be mutated in ways that change their hash code while they are in the map, or the entry becomes unreachable.
record Coordinate(int row, int col) { }

Map<Coordinate, String> board = new HashMap<>();
board.put(new Coordinate(0, 0), "X");
System.out.println(board.get(new Coordinate(0, 0)));   // X: records compare by value

Values have no such requirement; they can be any object.

Immutable Maps

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

Map.of and Map.copyOf create unmodifiable maps. They reject null keys and values and throw UnsupportedOperationException on modification. Map.of accepts up to 10 key-value pairs; for more, use Map.ofEntries(Map.entry(k, v), ...). To get a modifiable map from one, wrap it: new HashMap<>(Map.of(...)).

HashMap Versus Other Maps

HashMapLinkedHashMapTreeMap
Iteration orderNone guaranteedInsertion (or access) orderSorted by key
get / put / removeO(1) averageO(1) averageO(log n)
Key requirementsequals + hashCodeequals + hashCodeComparable or a Comparator
null keyOne allowedOne allowedNot with natural ordering
Extra featuresCan act as an LRU cachefirstKey, floorKey, ranges

For use from multiple threads, HashMap is not safe; use ConcurrentHashMap (covered in the multithreading section).

Practical Example: Order Statistics

import java.util.*;

public class OrderStats {
    record Order(String customer, String city, double amount) { }

    public static void main(String[] args) {
        List<Order> orders = List.of(
            new Order("ada", "Chennai", 1200),
            new Order("linus", "Bengaluru", 450),
            new Order("ada", "Chennai", 800),
            new Order("grace", "Chennai", 3000),
            new Order("linus", "Bengaluru", 1550),
            new Order("alan", "Pune", 999)
        );

        Map<String, Double> spendByCustomer = new HashMap<>();
        Map<String, Integer> ordersByCity = new HashMap<>();
        Map<String, Set<String>> customersByCity = new HashMap<>();

        for (Order o : orders) {
            spendByCustomer.merge(o.customer(), o.amount(), Double::sum);
            ordersByCity.merge(o.city(), 1, Integer::sum);
            customersByCity.computeIfAbsent(o.city(), c -> new TreeSet<>()).add(o.customer());
        }

        List<Map.Entry<String, Double>> ranking = new ArrayList<>(spendByCustomer.entrySet());
        ranking.sort(Map.Entry.<String, Double>comparingByValue().reversed()
                .thenComparing(Map.Entry.comparingByKey()));

        System.out.println("Top customers:");
        for (Map.Entry<String, Double> e : ranking) {
            System.out.printf("  %-6s %8.2f%n", e.getKey(), e.getValue());
        }
        for (String city : new TreeSet<>(ordersByCity.keySet())) {
            System.out.println(city + ": " + ordersByCity.get(city) + " orders from " + customersByCity.get(city));
        }
        System.out.println("Orders from Mumbai: " + ordersByCity.getOrDefault("Mumbai", 0));
    }
}

Output:

Top customers:
  grace   3000.00
  ada     2000.00
  linus   2000.00
  alan     999.00
Bengaluru: 2 orders from [linus]
Chennai: 3 orders from [ada, grace]
Pune: 1 orders from [alan]
Orders from Mumbai: 0

One pass over the orders builds three maps. merge with Double::sum accumulates each customer's spending, and with Integer::sum counts orders per city. computeIfAbsent creates a TreeSet for a city the first time it appears, then adds customers to it, removing duplicates and keeping names sorted. Because HashMap has no order, the ranking copies the entries into a list and sorts them by value, descending, with the key as a tie-breaker so that equal totals (ada and linus) always appear in the same order. Cities are printed by iterating over a sorted copy of the key set, and getOrDefault reports zero for a city with no orders instead of returning null.

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 HashMap and get answers drawn from it.

Signed-in readers only.