🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOGjava.lang.IllegalArgumentException: Illegal initial capacity: -1 at java.base/java.util.HashMap.<init>(HashMap.java:447) at java.base/java.util.HashMap.<init>(HashMap.java:470)

⚡ Quick Fix Works 80% of the time

Clamp computed capacities before constructing.

int cap = Math.max(1, expectedSize); Map<K, V> map = new HashMap<>((int) (cap / 0.75f) + 1);

🧠 Why this Happens

Tap to expand the deep technical explanation

HashMap(int) runs its guards before allocating anything: initialCapacity must be non-negative and at most MAXIMUM_CAPACITY (1 << 30), and loadFactor must be positive and finite ("Illegal load factor" is the sibling message). The value is then rounded up to a power of two during first resize. Negative numbers almost always originate in int arithmetic - dividing by 0.75f after an int multiply overflows, or a sentinel -1 flows from config into the constructor.

The HITEC City Parking Spot Analogy:

Booking a banquet hall for minus forty guests: the reservation desk rejects the form outright instead of guessing.

🔁 How to Reproduce Confirm this is your error

Map<String, Integer> m = new HashMap<>(-1); // IllegalArgumentException

🛠️ Solutions (5 Ways to Fix)

Solution 1✓ Most common cause

Clamp with Math.max before constructing

👉 Use this if capacity comes from user config or estimates that could be zero/negative.

A one-line lower bound keeps zero legal (zero is fine for HashMap) and blocks negatives from ever reaching the constructor.

int safe = Math.max(0, configuredSize); Map<String, Config> cache = new HashMap<>(safe);
Solution 2

Do the sizing math in long, cast last

👉 Use this if computing capacity as expected * growth / load-factor.

long intermediates cannot overflow int range; validate bounds THEN narrow to int, so impossible values fail your check instead of surfacing here.

long need = (long) expected * 4L / 3L + 1L; int cap = (need > (1 << 30)) ? (1 << 30) : (int) need;
Solution 3

Centralize a sized-map helper

👉 Use this if several call sites compute HashMap capacities.

One static method owns clamping plus the divide-by-load-factor formula; call sites stay honest and tests cover it once.

static <K, V> Map<K, V> withExpectedSize(int n) { return new HashMap<>((int) ((float) n / 0.75f) + 1); }
Solution 4

Default constructor when size is unknown

👉 Use this if no reliable estimate exists.

new HashMap<>() starts lazily and grows amortized O(1); premature micro-sizing causes more harm than good.

Map<K, V> map = new HashMap<>();
Solution 5

Validate configuration at startup

👉 Use this if sizes come from properties/env/yaml.

Fail fast with a message naming the property when parsed sizes fall outside 0..MAXIMUM_CAPACITY, so misconfiguration never reaches collection constructors.

if (configured < 0 || configured > MAX) { throw new IllegalArgumentException("cache.size invalid: " + configured); }

📋 Version Notes

Java 8

Same guard and wording; table sized on first resize.

Java 19+

Unchanged wording. Note HashMap(0) is legal and defers allocation until first put.

🛡️ How to Prevent This Next Time

Treat every constructor int argument as untrusted input: clamp or validate at the boundary where the number is born.