The three common ways to initialize HashMap in Java are the new HashMap<>() constructor for an empty map, HashMap.newHashMap(n) for a map that will hold n entries, and new HashMap<>(Map.of(…)) for a map that starts with fixed key-value pairs. Each choice also sets the initial capacity and the load factor, which decide when the map resizes its internal array.
We pick the initialization style by what we know up front. Settings with fixed defaults fit Map.of(), rows loaded from a database or a file fit newHashMap() or a stream collector, and a map filled step by step fits the default constructor. The following example shows every way the article covers, with the result of each line as a comment.
Map<String, Integer> empty = new HashMap<>(); // {}, 16 buckets on the first put
Map<String, Integer> presized = HashMap.newHashMap(100); // {}, holds 100 entries without a resize
Map<String, Integer> tuned = new HashMap<>(64, 0.5f); // {}, 64 buckets, resizes after 32 entries
Map<String, Integer> config = new HashMap<>(Map.of("timeout", 30, "retries", 3));
Integer previous = config.put("port", 8080); // null, the copy is mutable
int size = config.size(); // 3
Map<String, Integer> lengths = Stream.of("timeout", "port")
.collect(Collectors.toMap(k -> k, String::length, (a, b) -> a, HashMap::new));
Integer portLength = lengths.get("port"); // 4
Notice that Map.of() creates an unmodifiable map, so we copy it into a new HashMap<>(…) before adding the port entry. We start with capacity and load factor, compare the constructor with newHashMap(), and finish with pre-populated maps and stream collectors.
1. Quick Overview of Initial Capacity and Load Factor
When we look into the internal working of HashMap, we see two parameters that control its size, namely initialCapacity and loadFactor.
- The initial capacity is the number of buckets in the internal array. HashMap rounds it up to the next power of two, and the default initial capacity is 16. The array is created on the first put(), not in the constructor.
- The load factor is how full the map may get before it grows. The default load factor is 0.75, so the threshold is capacity x 0.75, which is 12 for 16 buckets.
When a put() takes the number of entries over the threshold, HashMap creates an array twice as long and moves every entry into it. A default map resizes when the 13th, 25th, 49th and 97th entries are added, and each resize walks the whole table.

Each resize costs time and creates garbage, which is why we pass an expected size when we know it. For a few dozen entries, the default constructor is fine, because the resizes are cheap.
2. Initialize an Empty HashMap
An empty map has no entries when it is created, and we fill it later with put(), putAll() or merge(). The default constructor is the usual way to create one, and it uses an initial capacity of 16 and a load factor of 0.75.
Map<String, Integer> settings = new HashMap<>();
Integer old = settings.put("timeout", 30); // null
Integer timeout = settings.get("timeout"); // 30
We can also pass the initial capacity, or the capacity and the load factor, to the constructor. Both values are rounded and checked, and invalid values throw IllegalArgumentException.
Map<String, Integer> ten = new HashMap<>(10); // 16 buckets, resizes on the 13th entry
Map<String, Integer> sparse = new HashMap<>(64, 0.5f); // 64 buckets, resizes on the 33rd entry
Map<String, Integer> negative = new HashMap<>(-1); // IllegalArgumentException: Illegal initial capacity
Map<String, Integer> zeroFactor = new HashMap<>(16, 0f); // IllegalArgumentException: Illegal load factor
A lower load factor such as 0.5 means fewer keys per bucket and faster lookups, but more memory for empty buckets. A higher value such as 0.9 saves memory and makes collisions more likely. The default of 0.75 is a good trade-off, so we change it only after measuring.
Since Java 19, HashMap.newHashMap(int) takes the number of entries we expect instead of a capacity. It is a static factory method, so we call it on the class instead of using new.
Map<String, Integer> forTen = HashMap.newHashMap(10); // holds 10 entries without a resize
Map<String, Integer> invalid = HashMap.newHashMap(-5); // IllegalArgumentException: Negative number of mappings: -5
3. Difference between new HashMap(capacity) Constructor and newHashMap(numMappings) Method
The constructor new HashMap<>(n) creates n buckets (rounded up to a power of two), whereas HashMap.newHashMap(n) creates enough buckets to store n entries without a resize. The difference is the load factor. With 0.75, a table of 128 buckets holds only 96 entries before it grows.
For example, we want to store 100 entries. The constructor new HashMap<>(100) rounds 100 up to 128 buckets, which gives a threshold of 96, so the 97th entry still triggers a resize. The method newHashMap(100) divides 100 by 0.75, rounds the result up to 134, and creates the next power of two, 256 buckets, with a threshold of 192.
| Expected entries | new HashMap<>(n) buckets / threshold | HashMap.newHashMap(n) buckets / threshold |
|---|---|---|
| 10 | 16 / 12 | 16 / 12 |
| 13 | 16 / 12, resizes on the 13th entry | 32 / 24 |
| 100 | 128 / 96, resizes on the 97th entry | 256 / 192 |
| 1,000 | 1024 / 768, resizes on the 769th entry | 2048 / 1536 |
The constructor sometimes fits by luck, for example 180 rounds up to 256 buckets with a threshold of 192, but newHashMap() fits every time. The same factory exists for LinkedHashMap as LinkedHashMap.newLinkedHashMap(int), and for HashSet as HashSet.newHashSet(int).
After the map is created, we add and read entries as usual.
Map<String, Integer> limits = HashMap.newHashMap(2);
Integer first = limits.put("uploadMb", 25); // null
Integer second = limits.put("users", 500); // null
Integer users = limits.get("users"); // 500
4. Initialize HashMap with Key-Value Pairs
When the key-value pairs are known in the code, we create an unmodifiable map with Map.of() or Map.ofEntries() and pass it to the copy constructor of HashMap. The copy constructor takes any Map and copies all its entries into a new, mutable HashMap.
Map<String, Integer> defaults = new HashMap<>(Map.of("timeout", 30, "retries", 3));
Integer timeout = defaults.get("timeout"); // 30
Integer old = defaults.put("timeout", 60); // 30, the copy can change
Map<String, Integer> many = new HashMap<>(Map.ofEntries(
Map.entry("timeout", 30),
Map.entry("retries", 3),
Map.entry("port", 8080)));
int count = many.size(); // 3
The Map.of() overloads take up to 10 key-value pairs, and Map.ofEntries() takes any number of Map.entry() pairs. Both reject null keys and values with NullPointerException, and a duplicate key with IllegalArgumentException. If we only need to read the pairs, we keep the result of Map.of() as it is, because an unmodifiable map protects the defaults from changes.
Map<String, Integer> duplicate = Map.of("timeout", 30, "timeout", 60); // IllegalArgumentException: duplicate key: timeout
Map<String, Integer> readOnly = Map.of("timeout", 30);
Integer changed = readOnly.put("timeout", 60); // UnsupportedOperationException
Older code fills a map with double-brace initialization, which puts put() calls in an extra pair of braces after new HashMap<>(). That syntax creates an anonymous subclass of HashMap each time, and the subclass holds a reference to the enclosing object, which can keep that object in memory. We use Map.of() or Map.ofEntries() with the copy constructor instead.
5. Collecting Stream Elements into HashMap
Often the key-value pairs come from a dynamic source such as a database result, a file or a list of records. The Stream API collects stream elements into a Map with Collectors.toMap(), which takes a key mapper and a value mapper.
record Setting(String name, int value) {}
List<Setting> rows = List.of(new Setting("timeout", 30), new Setting("retries", 3));
Map<String, Integer> byName = rows.stream().collect(Collectors.toMap(Setting::name, Setting::value));
Integer retries = byName.get("retries"); // 3
Integer added = byName.put("port", 8080); // null, the result is mutable
In JDK 25, the two-argument toMap() returns a HashMap, but the Collectors.toMap() Javadoc does not guarantee the type or the mutability. It also throws IllegalStateException when two elements produce the same key. When we need a HashMap for sure and the data can have duplicates, we use the four-argument version with a merge function and HashMap::new.
List<Setting> withDuplicates = List.of(new Setting("timeout", 30), new Setting("timeout", 45));
Map<String, Integer> failing = withDuplicates.stream().collect(Collectors.toMap(Setting::name, Setting::value)); // IllegalStateException: Duplicate key timeout
HashMap<String, Integer> lastWins = withDuplicates.stream()
.collect(Collectors.toMap(Setting::name, Setting::value, (oldValue, newValue) -> newValue, HashMap::new));
Integer timeout = lastWins.get("timeout"); // 45
If every key should keep all its values instead of one, we group the elements with Collectors.groupingBy(), which creates a HashMap of lists. More conversions of a list into a map, such as keeping the first value or sorting the keys, are in converting a List to a Map.
List<Setting> withDuplicates = List.of(new Setting("timeout", 30), new Setting("timeout", 45));
Map<String, List<Integer>> grouped = withDuplicates.stream()
.collect(Collectors.groupingBy(Setting::name, Collectors.mapping(Setting::value, Collectors.toList())));
List<Integer> timeouts = grouped.get("timeout"); // [30, 45]
6. Loading a Price List at Application Startup
An online shop loads 50,000 product prices from the database into a HashMap when the app starts, so the checkout code can read a price without a query. Created with the default constructor, the map resizes 13 times on the way to 50,000 entries, and every resize copies all entries collected so far.
Because the query result tells us the row count, we create the map with newHashMap() and fill it in one pass. The map never resizes, and we keep the type mutable so a price update event can replace a single entry later.
record Price(String sku, long cents) {}
List<Price> rows = List.of(new Price("tea", 450), new Price("mug", 1200));
Map<String, Long> prices = HashMap.newHashMap(rows.size());
rows.forEach(row -> prices.put(row.sku(), row.cents()));
Long mug = prices.get("mug"); // 1200
Long updated = prices.put("tea", 480L); // 450, price change event
7. HashMap Initialization FAQs
Capacity, one-line initialization and null values cause most of the confusion when we create a HashMap.
7.1. What is the default capacity and load factor of a HashMap?
The default capacity is 16 buckets and the default load factor is 0.75. The map resizes to 32 buckets when the 13th entry is added, because 16 x 0.75 = 12.
7.2. Is HashMap.newHashMap() better than new HashMap(n)?
Yes, when n is the number of entries we plan to store. The constructor treats n as a bucket count, so new HashMap<>(100) still resizes on the 97th entry. The factory method divides by the load factor first, so newHashMap(100) holds 100 entries without a resize. It needs Java 19 or later.
7.3. How do we initialize a HashMap with values in one line?
We copy an unmodifiable map into a new HashMap. This works for up to 10 pairs with Map.of(), and for any number with Map.ofEntries().
Map<String, Integer> ports = new HashMap<>(Map.of("http", 80, "https", 443));
Integer https = ports.get("https"); // 443
7.4. Does the initial capacity have to be a power of two?
No. We can pass any value from 0 to 230, and HashMap rounds it up to the next power of two when it creates the array. For example, new HashMap<>(10) gets 16 buckets and new HashMap<>(100) gets 128.
7.5. Can a HashMap created with Map.of() hold null?
No. Map.of() throws NullPointerException for a null key or value, so we cannot pass null pairs to it even when the target is a HashMap. We add null entries with put() after the copy.
Map<String, Integer> nullable = new HashMap<>(Map.of("timeout", 30));
Integer none = nullable.put("proxy", null); // null, HashMap accepts null values
boolean hasProxy = nullable.containsKey("proxy"); // true
8. Conclusion
A HashMap created with new HashMap<>() starts with 16 buckets and resizes each time its size passes capacity x 0.75. When we know how many entries are coming, HashMap.newHashMap(n) picks a capacity that avoids every resize, whereas new HashMap<>(n) only sets the bucket count.
For fixed pairs, Map.of() and Map.ofEntries() with the copy constructor give a mutable map in one statement. For records and other stream sources, Collectors.toMap() with a merge function and HashMap::new handles duplicate keys and guarantees the map type. The Java HashMap guide covers the operations we run on the map after it is created.
9. References
- HashMap Javadoc (Java 25)
- Map Javadoc (Java 25)
- Collectors Javadoc (Java 25)
- JDK-8186958: Need method to create pre-sized HashMap
Happy Learning !!