A Java HashMap is the general-purpose implementation of the Map interface that stores key-value pairs in a hash table, so it finds the value for a key in constant time on average. Each key appears once, values can repeat, one null key and any number of null values are allowed, and the iteration order is not defined.
We use a HashMap whenever we look things up by a key, for example a user by user name, a price by product code, or a counter by word. The class belongs to the Java Collections framework in the java.util package. The following example shows the everyday operations, with the result of each line as a comment.
Map<String, Integer> scores = new HashMap<>();
Integer added = scores.put("alex", 40); // null, new key
Integer replaced = scores.put("alex", 45); // 40, old value returned
Integer kept = scores.putIfAbsent("alex", 99); // 45, existing value stays
Integer alex = scores.get("alex"); // 45
Integer none = scores.get("sam"); // null, no such key
Integer orZero = scores.getOrDefault("sam", 0); // 0
Integer counted = scores.merge("maria", 10, Integer::sum); // 10, inserted
Integer summed = scores.merge("maria", 5, Integer::sum); // 15, added to the old value
boolean hasAlex = scores.containsKey("alex"); // true
Integer removed = scores.remove("alex"); // 45
int size = scores.size(); // 1
Notice that put() and remove() return the previous value, which tells us whether the key was there. We cover creating and sizing a map, the update methods added in Java 8, keys and their hashCode(), what happens inside the map, thread safety, and how HashMap compares with the other Map classes. The last sections link the detailed articles for each HashMap task.
1. What Is a HashMap in Java?
The HashMap class implements Map<K, V> and extends AbstractMap. Internally, it keeps an array of buckets, and the bucket of each entry comes from the hashCode() of its key, which is why get() and put() do not have to search the whole map. Five properties decide when it fits and when another map is the better choice.
- Keys are unique. A second put() with an equal key replaces the value, and equality comes from equals() and hashCode().
- One null key and any number of null values are allowed.
- The iteration order is not defined and can change when the map grows, so a HashMap is not a SequencedMap.
- It is not synchronized. Threads that share a HashMap and write to it need a ConcurrentHashMap or another thread-safe map.
- Its iterators are fail-fast, so changing the map structure during a loop throws ConcurrentModificationException.

The decision tree shows the questions that lead away from HashMap. If none of them applies, a HashMap is the fastest and simplest choice.
2. Creating a HashMap
A new HashMap is empty, starts with room for 16 buckets and grows by doubling when it is 75% full. The constructor we pick depends on what we know up front.
Map<String, Integer> empty = new HashMap<>(); // 16 buckets on the first put
Map<String, Integer> forThousand = HashMap.newHashMap(1_000); // Java 19+, no resize up to 1,000 entries
Map<String, Integer> filled = new HashMap<>(Map.of("alex", 40, "maria", 70));
Integer maria = filled.get("maria"); // 70
Map<String, Integer> copy = new HashMap<>(filled);
Integer old = copy.put("alex", 50); // 40, the original map still has 40
Integer original = filled.get("alex"); // 40
Map.of() returns an unmodifiable map, so we pass it to the copy constructor when we want to change the entries later. The copy constructor copies the entries, not the key and value objects, so a mutable value object is shared by both maps. The articles on deep copies and on sizing with newHashMap() and the load factor are listed in section 10.
3. Put, Get and Remove Operations
The basic methods come from the Map interface. Most of them return the previous value or null, so a null result means either that the key was absent or that it was mapped to null.
| Method | What it does | Returns |
|---|---|---|
| put(k, v) | Adds or replaces the mapping | Previous value or null |
| putIfAbsent(k, v) | Adds only if the key is absent or mapped to null | Existing value or null |
| putAll(map) | Copies all mappings of another map | Nothing |
| get(k) | Reads the value | Value or null |
| getOrDefault(k, d) | Reads the value with a fallback | Value or d |
| containsKey(k) / containsValue(v) | Checks for a key (fast) or a value (scans the map) | boolean |
| remove(k) / remove(k, v) | Removes the key, or the key only when it maps to v | Old value / boolean |
| replace(k, v) / replace(k, old, new) | Replaces only an existing mapping | Old value / boolean |
| size(), isEmpty(), clear() | Counts, checks or empties the map | int, boolean, nothing |
Map<String, String> cities = new HashMap<>();
String first = cities.put("lokesh", "Delhi"); // null
String moved = cities.replace("lokesh", "Pune"); // "Delhi"
String nobody = cities.replace("sam", "Rome"); // null, sam was never added
boolean wrong = cities.remove("lokesh", "Delhi"); // false, the value is "Pune"
boolean right = cities.remove("lokesh", "Pune"); // true
String nullKey = cities.put(null, "unknown"); // null, one null key is allowed
String readNull = cities.get(null); // "unknown"
When a map can hold null values, get() cannot tell a missing key from a null value, so we check with containsKey(). The method containsValue() walks every entry, which makes it slow on large maps.
4. Updating Values With compute() and merge()
Java 8 added methods to Map that read, change and write a value in one call. They replace the get-check-put pattern, which takes more code and needs its own null check.
| Method | Runs the function when | Typical use |
|---|---|---|
| merge(k, v, fn) | The key exists (absent keys get v) | Counters and sums |
| computeIfAbsent(k, fn) | The key is absent | Creating a list or cache value on first use |
| computeIfPresent(k, fn) | The key exists | Updating or removing an existing value |
| compute(k, fn) | Always | Any update that also handles the absent case |
| replaceAll(fn) | For every entry | Changing all values at once |
If the function returns null, the entry is removed. A Map<String, List<String>> filled with computeIfAbsent() is the standard way to keep several values per key.
Map<String, Integer> views = new HashMap<>();
Integer v1 = views.merge("home", 1, Integer::sum); // 1
Integer v2 = views.merge("home", 1, Integer::sum); // 2
Map<String, List<String>> tags = new HashMap<>();
boolean tagged = tags.computeIfAbsent("java", k -> new ArrayList<>()).add("streams"); // true
Integer doubled = views.computeIfPresent("home", (page, n) -> n * 2); // 4
Integer gone = views.compute("home", (page, n) -> null); // null, the entry is removed
boolean hasHome = views.containsKey("home"); // false
5. Looping Over Keys, Values and Entries
A HashMap offers three views to loop over, keySet(), values() and entrySet(), plus the forEach() method. For keys and values together, we use entrySet() or forEach(), because a keySet() loop that calls get() repeats a lookup for every key.
Map<String, Integer> scores = new HashMap<>(Map.of("alex", 40, "maria", 70));
int total = 0;
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
total += entry.getValue();
}
int sum = total; // 110
List<String> sortedNames = scores.keySet().stream().sorted().toList(); // [alex, maria]
boolean removed = scores.values().removeIf(score -> score < 50); // true
Removing entries through a view, as the last line does, is safe during iteration, whereas map.remove() inside a for-each loop throws ConcurrentModificationException. The iteration article in section 10 compares all ways to loop with a JMH run on JDK 25.
6. Choosing Keys With equals(), hashCode() and Records
A key class must override equals() and hashCode() together, using the same fields, because HashMap picks the bucket with hashCode() and finds the key in the bucket with equals(). Without the overrides, two objects with the same data are different keys. Records generate both methods from their components, which makes a record the simplest key type.
record AccountKey(String bank, int number) {}
Map<AccountKey, Long> balances = new HashMap<>();
Long first = balances.put(new AccountKey("hdfc", 1001), 500L); // null
Long read = balances.get(new AccountKey("hdfc", 1001)); // 500, an equal record finds the entry
Keys must also stay unchanged while they are in the map. If a field used by hashCode() changes after put(), get() searches a different bucket and returns null, although the entry is still there and still shows up in a loop. Records with immutable fields, String and the wrapper classes avoid that problem.
7. Buckets, Tree Bins and Resizing Inside a HashMap
A HashMap computes the bucket index as (n – 1) & hash, where n is the table length (always a power of two) and hash is the key’s hashCode() with its high bits mixed into the low ones. Keys that share a bucket form a linked list. Since Java 8 (JEP 180), a bucket that gets a 9th node turns into a red-black tree once the table has 64 buckets, so a crowded bucket costs O(log n) instead of O(n).
When the number of entries passes capacity x 0.75, the map doubles the table and moves every entry, which is why a known size passed to HashMap.newHashMap() saves work. The internals article in section 10 walks through put() and get() with the JDK 25 source.
| Operation | Average | Worst case |
|---|---|---|
| get(), put(), remove(), containsKey() | O(1) | O(log n) for a tree bucket of Comparable keys, O(n) otherwise |
| containsValue() | O(n) | O(n) |
| Iterating all entries | O(capacity + n) | O(capacity + n) |
| A put() that triggers a resize | O(n) for that call | O(n) |
8. HashMap and Threads
A HashMap is not thread-safe, so two threads that write to the same map can lose updates or leave it in a broken state. Reading from many threads is safe only when no thread writes after the map was safely published, for example a map built once at startup and never changed.
For shared maps that change, we use ConcurrentHashMap, which reads without locks and locks one bucket per write. Collections.synchronizedMap() wraps a HashMap with one lock for all calls. The atomic methods of the concurrent maps are explained in the ConcurrentMap guide.
ConcurrentHashMap<String, Integer> hits = new ConcurrentHashMap<>();
Integer h1 = hits.merge("home", 1, Integer::sum); // 1, safe from many threads
Map<String, Integer> fixed = Map.copyOf(Map.of("timeout", 30));
Integer t = fixed.get("timeout"); // 30, unmodifiable, safe to share
9. HashMap vs Other Map Implementations
Every Map class in the JDK solves one extra need on top of key lookup. Each alternative in the table links to its own article, and the columns show the need that each one adds.
| Class | Order | null keys | Thread-safe | Pick it when |
|---|---|---|---|---|
| HashMap | None | One | No | Fast lookups with no other need |
| LinkedHashMap | Insertion or access order | One | No | Predictable order, LRU caches |
| TreeMap | Sorted by key | No (natural order) | No | Sorted keys, range queries |
| ConcurrentHashMap | None | No | Yes | Shared, changing maps |
| EnumMap | Enum declaration order | No | No | Enum keys |
| WeakHashMap | None | One | No | Entries that disappear with their key |
| IdentityHashMap | None | One | No | Keys compared with == |
| Hashtable | None | No | Yes, one lock | Legacy code only |
The most common comparison is with the sorted map, which TreeMap vs HashMap covers with performance and memory numbers. A LinkedHashMap and a TreeMap are also SequencedMap instances since Java 21, so they offer firstEntry(), lastEntry() and reversed().
10. Common HashMap Tasks
Most day-to-day HashMap work falls into a few groups of tasks, and each task has a detailed article with tested examples. The first group is about creating, copying and combining maps.
- Initialize a HashMap with values, a size or a stream collector.
- Clone a HashMap as a shallow or deep copy.
- Merge two HashMaps and resolve duplicate keys.
- Compare two HashMaps for equal entries, keys or values.
- Nested maps such as Map<String, Map<String, Integer>>.
The second group reads and converts the data in a map.
- Find the largest or smallest value and the key that holds it.
- Convert a Map to an array, List or Set.
- Invert a Map so values become keys.
- Sort a Map by keys and sort a Map by values.
The third group explains how the class behaves under load, and the Java Map series lists every Map article on the site.
- computeIfAbsent() for caches and multi-value maps.
- How HashMap works internally, from hashCode() to tree bins.
- Iterate over a HashMap with measured performance.
- Synchronize a HashMap or use ConcurrentHashMap.
- Design a good HashMap key.
- HashMap and ConcurrentHashMap interview questions.
11. Blocking Repeated Login Failures With a HashMap
A login service locks an account for a few minutes after five wrong passwords in a row. The failure count per user name is a classic HashMap job, because every login attempt reads and updates one key, and the map never needs an order.
static boolean recordFailure(Map<String, Integer> failures, String user) {
int count = failures.merge(user, 1, Integer::sum);
return count >= 5; // true means lock the account
}
Map<String, Integer> failures = new HashMap<>();
boolean locked = false;
for (int i = 0; i < 5; i++) {
locked = recordFailure(failures, "alex");
}
boolean alexLocked = locked; // true
Integer reset = failures.remove("alex"); // 5, cleared after a successful login
In a real server, many request threads call recordFailure() at the same time, so the map must be a ConcurrentHashMap. The method signature takes a Map, so the code does not change, and merge() stays atomic on the concurrent map.
12. Java HashMap FAQs
Each answer gives the rule first and names the class to use when HashMap does not fit.
12.1. Can a HashMap have duplicate keys?
No. A key appears once, and a second put() with an equal key replaces the value and returns the old one. Values can repeat freely. To keep several values for one key, we store a collection as the value, for example with computeIfAbsent(key, k -> new ArrayList<>()).add(value).
12.2. Does HashMap allow null keys and values?
Yes. A HashMap allows one null key, which it stores in bucket 0, and any number of null values. ConcurrentHashMap, TreeMap with natural ordering, Hashtable and Map.of() reject null keys.
12.3. Does HashMap keep insertion order?
No. The order depends on the hash codes and the capacity, and it can change after a resize. LinkedHashMap keeps insertion order and TreeMap keeps keys sorted.
Map<String, Integer> ordered = new LinkedHashMap<>();
Integer a = ordered.put("maria", 70); // null
Integer b = ordered.put("alex", 40); // null
String firstKey = ordered.keySet().iterator().next(); // "maria"
12.4. What is the difference between HashMap and Hashtable?
Hashtable is a legacy class from Java 1.0 that synchronizes every method and rejects null keys and values. HashMap is not synchronized and accepts null. For a thread-safe map in new code, we use ConcurrentHashMap instead of Hashtable.
12.5. Is HashMap.get() O(1)?
On average, yes. With a reasonable hashCode(), each bucket holds very few entries. In the worst case, many keys share a bucket, and lookups cost O(log n) for Comparable keys in a tree bucket or O(n) for other keys.
13. Conclusion
A HashMap is the default Map in Java, with average constant-time lookups, unique keys, null support and no defined order. We create it with new HashMap<>(), with HashMap.newHashMap(n) for a known size, or as a mutable copy of Map.of().
For updates, merge() and the compute methods replace get-check-put code, and records make the safest keys because they generate equals() and hashCode() and stay unchanged. When we need order, sorting, enum keys or several threads, we switch to LinkedHashMap, TreeMap, EnumMap or ConcurrentHashMap.
14. References
- HashMap Javadoc (Java 25)
- Map Javadoc (Java 25)
- JEP 431: Sequenced Collections
- JEP 180: Handle Frequent HashMap Collisions with Balanced Trees
Happy Learning !!