What is a HashMap in Java?

What is a HashMap in Java?

9 mins read
Oct 31, 2025
Share
editor-page-cover
Content
Introduction
Java HashMap: Behind the scenes
Capacity, load factor tuning and rehashing strategy
Key tuning strategies
Java 8+ enhancements: Treeification for performance
Key points
Designing hashCode() and equals(): the key to performance
Best practices
Implementing different HashMap functions
Iterating over a Java HashMap
Data structures equivalent to Java HashMap in different programming languages
Advantages of HashMap
Disadvantages of HashMap
Thread safety and concurrent maps
Thread-safe alternatives
Example usage
Map variants and when to use them
Common map variants
Choosing the right map
Conclusion and next steps

Introduction#

In the realm of Java programming, data structures play a vital role in efficiently organizing and manipulating data. One such indispensable data structure is the Java HashMap. With its powerful capabilities and versatile nature, HashMaps have become a cornerstone of Java development. In this blog, we will explore what the Java HashMap is, including coding examples, their benefits, and potential disadvantages.

Java HashMap: Behind the scenes#

A Java HashMap stores key-value pairs where each key is different. This makes it easier and faster to find the value.

It achieves efficient storage and retrieval of key-value pairs by combining hash tables and linked lists in its back-end implementation. Let's dive into the details.

Buckets and Hashing

  • The HashMap internally uses an array of buckets to store the elements.

An array of buckets storing elements
An array of buckets storing elements
  • Each bucket is capable of holding multiple elements because of possible hash collisions. Note that collisions can happen when multiple keys have the same code.

  • The value for the key (i.e., the hash code) is calculated using the hashCode() method whenever a key-value pair is added to the HashMap. The value of the key is used to determine the specific bucket where the element will be added in the list.

  • Java’s hashCode() function spreads the hash codes uniformly across the array to minimize collisions and improve performance.

Linked list nodes

  • Each bucket in the HashMap is basically a linked list of nodes.

Linked list of nodes
Linked list of nodes
  • Whenever two items have the same code and need to go in the same bucket (otherwise known as a collision), we just add the new items to the front of that list.

  • In the linked list, each node holds both the key-value information and a reference to the next node that comes after it in the list.

Load factor and rehashing

  • The HashMap maintains a load factor that represents the ratio of filled buckets to the total number of buckets.

  • When the load factor exceeds a certain threshold (the default is 0.75), the HashMap automatically increases the size of the underlying bucket array and hashes all the elements again. This process is called rehashing.

  • Rehashing involves creating a new array with a larger capacity, recalculating the hash codes for all the keys, and redistributing the elements across the new array. This helps maintain a balanced distribution of elements and keeps the average bucket size small, improving performance.

Capacity, load factor tuning and rehashing strategy#

By default, a HashMap starts with capacity 16 and load factor 0.75.
But smart tuning can significantly improve performance for your use case.

Key tuning strategies#

  • Initial capacity:
    If you know the expected number of entries, set
    initialCapacity = (int)(expected / loadFactor) + 1
    to avoid costly resizes.

  • Lowering load factor:
    Reducing the load factor (e.g., to 0.5) lowers collisions
    but increases memory overhead — a trade-off of memory for speed.

  • Custom thresholds:
    Resizing is expensive since it requires rehashing all entries.
    For real-time systems, avoid frequent rehashes by oversizing initially.

  • Resize doubling:
    HashMap doubles its bucket array on resize.
    That means load factor thresholds are tied to capacity doubling.

  • Predict worst-case:
    In high-throughput maps, prevent peak rehash times by pre-sizing
    or keeping spare headroom.

These tuning strategies help prevent performance hiccups
for large or long-lived HashMaps.

Java 8+ enhancements: Treeification for performance#

In Java 8 and later, HashMap introduces an important enhancement: tree bins.
When a single bucket’s linked list grows beyond a threshold (default 8),
and the table size is sufficiently large (minimum capacity 64),
the bucket is converted from a linked list into a red-black tree.

This ensures that in the worst case, operations degrade to O(log n) instead of O(n).
When collisions are many, treeification prevents pathological performance degradation.

Key points#

  • The conversion threshold is controlled by TREEIFY_THRESHOLD (default 8)
    and MIN_TREEIFY_CAPACITY (default 64).

  • If table capacity is small, a bucket won’t treeify — even with many entries —
    until rehashing increases the table size.

  • On removal or resizing, a tree can revert to a linked list
    if elements fall below UNTREEIFY_THRESHOLD (default 6).

  • Tree nodes use a subclass TreeNode<K,V>, which preserves ordering
    (by key’s Comparable or hash tie) and integrates with existing chaining logic.

This change lets modern HashMap handle collision floods more reliably
under adversarial keys.

Designing hashCode() and equals(): the key to performance#

The correctness and performance of any HashMap usage heavily depend on
properly implementing hashCode() and equals() for your key objects.

Best practices#

  • Consistent contract:
    If two objects are equal per equals(), they must return the same hashCode().
    Violations break lookup correctness.

  • Good hash spread:
    Combine fields in a way that spreads bits; avoid poor distributions
    (e.g., values that are always multiples of 16).

  • Immutable keys:
    Keys should remain immutable while used in a map.
    Changing fields used in hashCode() after insertion will make the key “lost.”

  • Use Objects.hash(...) or manual combine:
    Combine multiple fields via bit shifts, XOR, or multipliers.
    For performance-critical code, avoid creating arrays in hashCode().

  • Avoid collisions:
    Low-collision hash codes reduce the burden on treeification
    and keep average complexity tight.

  • Overrides that call super:
    Avoid including super.hashCode() unless necessary,
    as it can spoil a clean hash distribution.

If a badly written hashCode() causes many keys to land in the same bucket,
performance will degrade — even with the treeification fallback in place.

Implementing different HashMap functions#

Let’s take a look at some code examples to better understand how the Java HashMap works.

HashMap in Java
HashMap in Java

put(key, value): The function adds a key-value pair to the HashMap. If the key is already there, it simply updates its associated value with the new one.

Java
import java.util.HashMap;
public class main {
public static void main(String[] args) {
// Creating a HashMap
HashMap<String, Integer> studentScores = new HashMap<>();
// Adding key-value pairs
studentScores.put("John", 95);
studentScores.put("Emily", 87);
studentScores.put("Michael", 92);
System.out.println(studentScores);
//updating John's score.
//Note that the key "John" already exists.
//The score is simply updated
studentScores.put("John", 85);
System.out.println(studentScores);
}
}

get(key): This retrieves the value associated with the specified key. If the key is not found, it returns null.

Java
import java.util.HashMap;
public class main {
public static void main(String[] args) {
// Creating a HashMap
HashMap<String, Integer> studentScores = new HashMap<>();
// Adding key-value pairs
studentScores.put("John", 95);
studentScores.put("Emily", 87);
studentScores.put("Michael", 92);
// Retrieving a value using a key
int johnScore = studentScores.get("John");
System.out.println("John's score: " + johnScore);
}
}

containsKey(key): This checks if the HashMap contains a specific key. It returns true if the key is present; otherwise, it returns false.

Java
import java.util.HashMap;
public class main {
public static void main(String[] args) {
// Creating a HashMap
HashMap<String, Integer> studentScores = new HashMap<>();
// Adding key-value pairs
studentScores.put("John", 95);
studentScores.put("Emily", 87);
studentScores.put("Michael", 92);
// Checking if a key exists
boolean containsEmily = studentScores.containsKey("Emily");
System.out.println("Contains Emily? " + containsEmily);
}
}

containsValue(value): This checks if the HashMap contains a specific value. It returns true if the value is present; otherwise, it returns false.

Java
import java.util.HashMap;
public class main {
public static void main(String[] args) {
// Creating a HashMap
HashMap<String, Integer> studentScores = new HashMap<>();
// Adding key-value pairs
studentScores.put("John", 95);
studentScores.put("Emily", 87);
studentScores.put("Michael", 92);
// Checking if a value exists
boolean containsScore92 = studentScores.containsValue(92);
System.out.println("Contains score 92? " + containsScore92);
}
}

remove(key): This removes the key-value pair associated with the specified key from the HashMap. It returns the value that was removed, or null if the key was not found.

Java
import java.util.HashMap;
public class main {
public static void main(String[] args) {
// Creating a HashMap
HashMap<String, Integer> studentScores = new HashMap<>();
// Adding key-value pairs
studentScores.put("John", 95);
studentScores.put("Emily", 87);
studentScores.put("Michael", 92);
// Removing a key-value pair
Integer removedScore = studentScores.remove("Michael");
System.out.println("Removed score: " + removedScore);
}
}

size(): This returns the number of key-value pairs currently stored in the HashMap.

Java
import java.util.HashMap;
public class main {
public static void main(String[] args) {
// Creating a HashMap
HashMap<String, Integer> studentScores = new HashMap<>();
// Adding key-value pairs
studentScores.put("John", 95);
studentScores.put("Emily", 87);
studentScores.put("Michael", 92);
System.out.println("Size of the HashMap: " + studentScores.size());
}
}

isEmpty(): This checks if the HashMap is empty and returns true if the HashMap contains no elements; otherwise, it returns false.

Java
import java.util.HashMap;
import java.util.Set;
public class main {
public static void main(String[] args) {
// Creating a HashMap
HashMap<String, Integer> studentScores = new HashMap<>();
// Adding key-value pairs
studentScores.put("John", 95);
studentScores.put("Emily", 87);
studentScores.put("Michael", 92);
// Retrieving all the keys
System.out.println("Is HashMap empty? " + studentScores.isEmpty());
}
}

keySet(): This returns a set containing all the keys in the HashMap.

Java
import java.util.HashMap;
import java.util.Set;
public class main {
public static void main(String[] args) {
// Creating a HashMap
HashMap<String, Integer> studentScores = new HashMap<>();
// Adding key-value pairs
studentScores.put("John", 95);
studentScores.put("Emily", 87);
studentScores.put("Michael", 92);
// Retrieving all the keys
Set<String> keys = studentScores.keySet();
System.out.println("Keys in the HashMap: " + keys);
}
}

values(): This returns a collection containing all the values in the HashMap.

Java
import java.util.HashMap;
import java.util.Collection;
public class main {
public static void main(String[] args) {
// Creating a HashMap
HashMap<String, Integer> studentScores = new HashMap<>();
// Adding key-value pairs
studentScores.put("John", 95);
studentScores.put("Emily", 87);
studentScores.put("Michael", 92);
// Retrieving all the values
Collection<Integer> values = studentScores.values();
System.out.println("Values in the HashMap: " + values);
}
}

entrySet(): This returns a set containing all the key-value pairs (entries) in the HashMap.

Java
import java.util.HashMap;
import java.util.Set;
public class main {
public static void main(String[] args) {
// Creating a HashMap
HashMap<String, Integer> studentScores = new HashMap<>();
// Adding key-value pairs
studentScores.put("John", 95);
studentScores.put("Emily", 87);
studentScores.put("Michael", 92);
// Retreiving all enties
Set<HashMap.Entry<String, Integer>> entries = studentScores.entrySet();
System.out.println("Entries in the HashMap: " + entries);
}
}

clear(): This removes all the key-value pairs from the HashMap, making it empty.

Java
import java.util.HashMap;
import java.util.Set;
public class main {
public static void main(String[] args) {
// Creating a HashMap
HashMap<String, Integer> studentScores = new HashMap<>();
// Adding key-value pairs
studentScores.put("John", 95);
studentScores.put("Emily", 87);
studentScores.put("Michael", 92);
// Clearing the HashMap
studentScores.clear();
System.out.println("HashMap cleared. Size: " + studentScores.size());
}
}

Note: HashMaps do not guarantee any specific order of elements. If there is a need to maintain a specific order, the LinkedHashMap class can be considered instead.

The following table summarizes the time complexities of the functions mentioned above found in the official Java documentationOracle. 2019. “HashMap (Java Platform SE 8 ).” Oracle.com. September 11, 2019. https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html..

Operation

Time Complexity

put(key, value)

O(1) average–O(n) worst case

get(key)

O(1) average–O(n) worst case

containsKey(key)

O(1) average–O(n) worst case

containsValue(value)

O(n)

remove(key)

O(1) average–O(n) worst case

size()

O(1)

isEmpty()

O(1)

keySet()

O(n)

values()

O(n)

entrySet()

O(n)

clear()

O(1)


Note that the HashMap class offers additional methods and functionalities that can be explored in the official Java documentationOracle. 2019. “HashMap (Java Platform SE 8 ).” Oracle.com. September 11, 2019. https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html. to fully leverage the power of this data structure in Java programs.

Iterating over a Java HashMap#

To iterate over a HashMap, there are various approaches that can be chosen.

Java
import java.util.HashMap;
import java.util.Iterator;
public class HashMapIterationExample {
public static void main(String[] args) {
// Create a HashMap
HashMap<String, Integer> studentScores = new HashMap<>();
// Add elements to the HashMap
studentScores.put("John", 95);
studentScores.put("Emily", 87);
studentScores.put("Michael", 92);
// Iterating using EntrySet()
System.out.println("Iterating using EntrySet():");
for (HashMap.Entry<String, Integer> entry : studentScores.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println("Key: " + key + ", Value: " + value);
}
// Iterating using KeySet()
System.out.println("\nIterating using KeySet():");
for (String key : studentScores.keySet()) {
Integer value = studentScores.get(key);
System.out.println("Key: " + key + ", Value: " + value);
}
// Iterating using Iterator
System.out.println("\nIterating using Iterator:");
Iterator<HashMap.Entry<String, Integer>> iterator = studentScores.entrySet().iterator();
while (iterator.hasNext()) {
HashMap.Entry<String, Integer> entry = iterator.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println("Key: " + key + ", Value: " + value);
}
}
}

In the example above, we create a class called HashMapIterationExample containing a main method. The necessary classes, HashMap and Iterator from the java.util package, are imported.

Inside the main method, we create a HashMap<String, Integer> called studentScores and add some key-value pairs to it.

The different ways to iterate over the HashMaps are as follows:

  • Lines 15–20: In this approach, the entrySet() method returns a set of key-value pairs (Map.Entry) in the HashMap. The for-each loop then iterates over each entry, allowing access to the key via getKey(), and the value via getValue().

  • Lines 23–27: The keySet() method returns a set of keys present in the HashMap. We can then use each key to retrieve the corresponding value using the get(key) method.

  • Lines 30–37: This approach involves obtaining an iterator using entrySet().iterator() and then iterating over the HashMap using a while loop. Similar to the first method, the key and the value can be accessed using getKey() and getValue(), respectively.

Data structures equivalent to Java HashMap in different programming languages#

What is the Java HashMap? The Java HashMap is a popular and powerful data structure in the Java programming language. However, when working with other programming languages, we have some equivalent data structures to choose from.

Data structures equivalent to a HashMap in other programming languages
Data structures equivalent to a HashMap in other programming languages

In Python, the data structure equivalent to a Java HashMap is the dictionary, which allows us to store key-value pairs where keys are unique and values can be of any type.

In C++, the unordered_map from the Standard Template Library (STL) provides a functionality similar to that of a HashMap. It offers fast insertion, retrieval, and deletion operations.

In JavaScript, objects can be used as data structures similar to HashMap. Keys can be strings or symbols, and values can be of any type.

Below is an example of the codes in C++, Python, and JavaScript.

#include <iostream>
#include <unordered_map>
int main() {
// Creating an unordered_map
std::unordered_map<std::string, int> student_scores = {
{"John", 95},
{"Emily", 87},
{"Michael", 92}
};
// Accessing a value using a key
int john_score = student_scores["John"];
std::cout << "John's score: " << john_score << std::endl;
// Updating a value
student_scores["Emily"] = 90;
// Removing a key-value pair
student_scores.erase("Michael");
return 0;
}
HashMap-equivalent code in C++

Advantages of HashMap#

Let’s look at the advantages of using HashMap.

Fast retrieval

It offers constant-time performance for basic operations such as insertion, retrieval, and deletion, making them highly efficient for handling large datasets.

Flexible key-value pairing

It allows the association of any object as a key and any object as a value, enabling developers to create custom data structures for specific use cases.

No duplicate keys

It enforces uniqueness of keys, ensuring that no duplicate keys exist within the collection.

Dynamic sizing

It automatically adjusts their size to accommodate more elements, eliminating the need for manual resizing.

Disadvantages of HashMap#

While HashMap provides numerous advantages, it’s essential to be aware of its potential limitations.

No preserved order

It does not guarantee any particular order of elements. If we require ordered key-value pairs, we need to consider using a LinkedHashMap instead.

Null key limitation

It allows a single null key but can't have duplicate null keys.

Hash collision performance

In rare cases, different keys may hash to the same index, resulting in a collision. This situation can degrade performance, especially when dealing with a large number of collisions. However, Java's HashMap implementation uses techniques like separate chaining and balanced trees to handle collisions efficiently.

Thread safety

It is not inherently thread-safe. If the application involves multiple threads accessing or modifying the map concurrently, we need to use ConcurrentHashMap, which provides thread-safe operations without the need for external synchronization.

Thread safety and concurrent maps#

A major limitation of HashMap is its lack of built-in thread safety.
Concurrent writes or mixed reads and writes can lead to data corruption
or even infinite loops in internal chains.
If you need thread-safe maps, use one of these approaches:

Thread-safe alternatives#

  • ConcurrentHashMap:
    Supports concurrent reads and writes using lock striping and non-blocking reads.
    Does not allow null keys or values.

  • Collections.synchronizedMap(…):
    Wraps a map with synchronization — simple but uses coarse-grained locking (global lock).

  • Concurrent alternatives:
    Depending on the use case, ConcurrentSkipListMap or CopyOnWriteArrayList (for small maps) may suffice.

Example usage#

Map<K, V> map = new ConcurrentHashMap<>();
map.put(key, value);
V v = map.get(key); // thread-safe

If you ever use a vanilla HashMap in a multi-threaded context,
wrap it or switch to a concurrent implementation to ensure safety.

Map variants and when to use them#

HashMap is a great default, but the Java ecosystem offers variants
with extra semantics or ordering guarantees.

Common map variants#

  • LinkedHashMap:
    Maintains insertion order (or access order) while retaining hash performance.
    Use for LRU caches.

  • TreeMap:
    Implements NavigableMap, sorted by key (natural or comparator).
    Operations are O(log n) rather than constant time.

  • WeakHashMap:
    Keys are referenced weakly, and entries can be garbage-collected
    when keys are no longer strongly reachable.

  • IdentityHashMap:
    Compares keys by reference (==) rather than equals().
    Useful for certain identity-based caching cases.

  • EnumMap:
    Specialized map for enum keys — offers high performance and compact storage.

  • ConcurrentSkipListMap:
    A thread-safe, sorted map (like a concurrent version of TreeMap).

Choosing the right map#

Select the right map variant based on your needs —
ordered iteration, memory lifecycle, identity-based keys, or concurrency.

Conclusion and next steps#

The Java HashMap offers a powerful and efficient way to store, retrieve, and manipulate data using key-value pairs. With its flexibility and high performance, it has become an indispensable tool for Java developers. By understanding the benefits and potential limitations of the HashMap, its strengths can be leveraged to create robust and efficient applications.

We hope this experience was fun and helped you learn about HashMap in Java. If you are looking to explore other data types and how they are used in different programming languages, we recommend checking out the following Educative courses:

Learn to Code: Java for Absolute Beginners

Cover
Learn to Code: Java for Absolute Beginners

Java is a high-level, object-oriented language known for its portability and reliability. Mastering Java is key for developers to build scalable, secure software efficiently. In this course, you will start by mastering the art of problem-solving with simple Java programs, such as the Bottle Filling example. You will learn how to structure your solutions, create execution sheets, and enhance your problem-solving abilities through practical exercises and quizzes. Progressing further, you will learn decision-making and branching in Java, understanding flowcharts, conditional expressions, and their application. You will also learn about Java basics, including the first Java program, fundamentals, conditional statements, and error categories, followed by in-depth lessons on working with Java strings and arrays. By the end of this course, you will have a solid understanding of Java programming fundamentals and the ability to solve real-world problems using Java.

8hrs
Beginner
4 Challenges
6 Quizzes

Learn C++: The Complete Course for Beginners

Cover
Learn C++: The Complete Course for Beginners

If you're a beginner and want to learn C++ to start your coding journey, you're in the right place. This comprehensive course starts from the absolute basics and gradually builds up to exciting real-life coding projects. The emphasis throughout is on practical lessons and analogies that you can relate to. As you learn, you'll work your way through dozens of coding exercises that you can run from right inside your browser. By the time you're done, you'll have a strong grasp of C++, one of the most in-demand programming languages in the world. You'll have gotten plenty of hands-on practice and will be able to confidently write your own applications.

10hrs
Beginner
33 Challenges
67 Quizzes

Python 3: From Beginner to Advanced

Cover
Python 3: From Beginner to Advanced

In this course, you will get a comprehensive overview of Python. You will start by laying the foundation and learning the introductory topics like operators, conditional statements, functions, loops, assignment statements, and more. You will then move onto more advanced topics such as classes and inheritance, iterators, list comprehensions, and dictionaries. Along with this, you will also get to explore the concept of testing and take a look at how GUI applications can be designed using Tkinter, Python's standard GUI library. By the time you're done, you'll be able to hit the ground running and be equipped with the tools that will allow you to be productive in Python.

4hrs
Beginner
7 Challenges
7 Quizzes

Written By:
Kamran Lodhi