What happens when an int overflows, and why does Math.abs sometimes return a negative number?
Nothing happens — it wraps silently. Integer.MAX_VALUE + 1 is Integer.MIN_VALUE, with no exception and no warning. And because the range is asymmetric, MIN_VALUE has no positive counterpart, so Math.abs(Integer.MIN_VALUE) returns MIN_VALUE — a negative result from a method that promises a magnitude. Use long, or Math.addExact when you need it to fail loudly.
The Answer
- An
intis 32 bits, holding −2,147,483,648 to 2,147,483,647. Arithmetic past either end wraps around — no exception, no warning, no log line. Integer.MAX_VALUE + 1isInteger.MIN_VALUE. The bits carry into the sign position and the number becomes negative.- The range is asymmetric: there is one more negative value than positive, because zero takes a slot on the positive side.
- So
MIN_VALUEhas no positive counterpart, andMath.abs(Integer.MIN_VALUE)returnsInteger.MIN_VALUE— negative. Documented, and a real source of bugs. - Fixes, in order of preference: use
long; useMath.addExactand friends, which throw; or check the bound before the operation. - Integer division truncates toward zero, so
-7 / 2is-3and-7 % 2is-1.
Understand It
Wrapping is the specified behaviour
Java's integer arithmetic is defined as two's-complement with wraparound. It is not undefined behaviour, it is not a warning, and it is not checked — because checking every addition would cost performance on the most common operation in any program.
int max = Integer.MAX_VALUE;
System.out.println("MAX_VALUE = " + max);
System.out.println("MAX_VALUE + 1 = " + (max + 1));
System.out.println("MIN_VALUE - 1 = " + (Integer.MIN_VALUE - 1));MAX_VALUE = 2147483647
MAX_VALUE + 1 = -2147483648
MIN_VALUE - 1 = 2147483647Adding one to the largest int gives the smallest. The arithmetic is exact
modulo 2³²; it is your interpretation of the bits as signed that flips.
The asymmetry, and what it does to Math.abs
32 bits give 4,294,967,296 distinct values. Split them around zero and you cannot do it evenly: zero occupies one of the "positive" slots, so the range runs from −2,147,483,648 to +2,147,483,647. One more negative than positive.
That one missing value has a consequence that surprises everyone:
System.out.println("Math.abs(MIN_VALUE) = " + Math.abs(Integer.MIN_VALUE));
System.out.println("-MIN_VALUE = " + (-Integer.MIN_VALUE));
System.out.println("is the result < 0? = " + (Math.abs(Integer.MIN_VALUE) < 0));Math.abs(MIN_VALUE) = -2147483648
-MIN_VALUE = -2147483648
is the result < 0? = trueMath.abs returned a negative number. It is not a bug — the positive value
2147483648 does not exist as an int, so there is nothing correct to
return, and the method documents that it returns the argument unchanged in
this one case. Negation has the same problem: -MIN_VALUE is MIN_VALUE.
This breaks real code. The pattern Math.abs(key.hashCode()) % buckets is
common and wrong: hashCode() can return Integer.MIN_VALUE, abs hands it
straight back, and % then yields a negative index, producing an
ArrayIndexOutOfBoundsException on a good day and silent misbehaviour on a
bad one. Math.floorMod(hash, buckets) is the correct form.
The overflow that looks like a constant
This is the shape that reaches production, because it looks like arithmetic a compiler would surely fold correctly:
int millisPerDay = 24 * 60 * 60 * 1000;
int millisPer30Days = 30 * 24 * 60 * 60 * 1000;
System.out.println("ms per day = " + millisPerDay);
System.out.println("ms per 30 days = " + millisPer30Days + " (should be 2,592,000,000)");
System.out.println("as long = " + 30L * 24 * 60 * 60 * 1000);ms per day = 86400000
ms per 30 days = -1702967296 (should be 2,592,000,000)
as long = 2592000000Every operand is a small, obviously-safe literal. The product is not.
2,592,000,000 exceeds Integer.MAX_VALUE by about 20%, so the expression
wraps to a negative number — and as a timeout or a cache TTL, a negative
duration behaves very differently from a long one.
The fix is one character: make the first operand a long. Order matters,
because the expression is evaluated left to right and the type is decided per
operation. 30 * 24 * 60 * 60 * 1000L overflows in int first and then widens
the already-wrong result.
Division truncates, and it truncates toward zero
Not floor — toward zero, which differs for negatives:
System.out.println("7 / 2 = " + (7 / 2));
System.out.println("-7 / 2 = " + (-7 / 2));
System.out.println("-7 % 2 = " + (-7 % 2));
System.out.println("(int) 3.9 = " + (int) 3.9);
System.out.println("(int) -3.9 = " + (int) -3.9);
System.out.println("(int) 3_000_000_000L = " + (int) 3_000_000_000L);7 / 2 = 3
-7 / 2 = -3
-7 % 2 = -1
(int) 3.9 = 3
(int) -3.9 = -3
(int) 3_000_000_000L = -1294967296-7 / 2 is -3, not -4, and % follows so that (a / b) * b + a % b == a
holds. The consequence: % can return a negative number, so
hash % buckets is unsafe as an index even before Math.abs gets involved.
Math.floorMod gives the non-negative answer people expect.
The last line is the other silent truncation: casting a long to int keeps
the low 32 bits and discards the rest, with no complaint.
Reference
Making overflow loud, when correctness matters more than speed:
// Throws ArithmeticException("integer overflow") instead of wrapping
int sum = Math.addExact(a, b);
int product = Math.multiplyExact(a, b);
int diff = Math.subtractExact(a, b);
int negated = Math.negateExact(a); // throws on MIN_VALUE
int narrowed = Math.toIntExact(someLong); // throws if it will not fit
// Saturating instead of throwing — clamps to MIN_VALUE / MAX_VALUE
int clamped = Math.clamp(someLong, Integer.MIN_VALUE, Integer.MAX_VALUE);
try { Math.addExact(Integer.MAX_VALUE, 1); }
catch (ArithmeticException e) { System.out.println("addExact -> " + e.getMessage()); }
try { Math.negateExact(Integer.MIN_VALUE); }
catch (ArithmeticException e) { System.out.println("negateExact -> " + e.getMessage()); }
try { Math.toIntExact(3_000_000_000L); }
catch (ArithmeticException e) { System.out.println("toIntExact -> " + e.getMessage()); }addExact -> integer overflow
negateExact -> integer overflow
toIntExact -> integer overflowThe patterns to fix on sight:
// WRONG // RIGHT
Math.abs(hash) % buckets Math.floorMod(hash, buckets)
(lo + hi) / 2 lo + (hi - lo) / 2
x - y // comparator Integer.compare(x, y)
30 * 24 * 60 * 60 * 1000 30L * 24 * 60 * 60 * 1000
int total = price * qty; long total = (long) price * qty;
count++ // may wrap Math.incrementExact(count)
Choosing a type by range:
| Type | Range | Use when |
|---|---|---|
int | ±2.1 billion | counts, indices, ids under ~2 billion |
long | ±9.2 quintillion | money in minor units, timestamps in millis, byte counts, any id that grows |
BigInteger | unbounded | cryptography, factorials, anything provably unbounded |
If a value is a count of things that could exceed two billion — rows, bytes,
milliseconds over a long span — it is a long. Two billion is not a large
number any more.
Scenarios
A timeout computed in int milliseconds. days * 24 * 60 * 60 * 1000
overflows past 24 days, producing a negative timeout. Depending on the API that
is an immediate return, an exception, or an effectively infinite wait — three
different bugs from one expression.
Hash bucket index going negative. Math.abs(hashCode()) % size works for
every key until one hashes to Integer.MIN_VALUE, then abs returns it
unchanged and the index is negative. Rare, data-dependent, and it looks like
memory corruption rather than arithmetic.
A row counter in a growing table. int count works for years and then
quietly wraps, and the report shows a negative total. The bug was written years
before it fired, which is the characteristic shape of overflow bugs.
Where int is still right. Array indices — Java arrays cannot exceed
Integer.MAX_VALUE elements anyway — loop counters over bounded collections,
and ordinary domain values with a known ceiling. Reaching for long everywhere
costs memory and readability for no benefit.
Interviewer's Next Move
1. "Why doesn't the JVM throw on overflow?"
Because integer addition is the single most common operation in any program,
and checking each one would cost measurable performance for a case most code
never hits. The JLS therefore defines wraparound rather than leaving it
undefined, and offers Math.addExact for when you want the check. It is a
deliberate trade, not an oversight.
2. "Explain Math.abs(Integer.MIN_VALUE)."
The range is asymmetric — one more negative value than positive — so
+2147483648 is not an int. There is no correct answer to return, and the
method documents that it returns the argument unchanged. Use
Math.absExact to have it throw, or work in long.
3. "Is -7 % 2 equal to 1 or -1?"
-1. Java truncates division toward zero and defines % to keep
(a / b) * b + a % b == a true, so the remainder takes the sign of the
dividend. Math.floorMod(-7, 2) gives 1, which is what you want for wrapping
an index.
4. "Where does this interact with binary search?"
(lo + hi) / 2 overflows once both indices are large, producing a negative
mid and an ArrayIndexOutOfBoundsException. It was in the JDK's own
Arrays.binarySearch for nine years. lo + (hi - lo) / 2 cannot overflow,
because hi - lo is a difference of two non-negative ints.
5. "How do you detect this in an existing codebase?"
Look for multiplication of int values where the factors are durations, sizes
or counts, and for Math.abs feeding a %. SpotBugs and Error Prone both
report the abs-then-modulo pattern. Overflow itself is not statically
detectable in general, which is why range-appropriate types beat auditing.
Check Yourself
Integer.MAX_VALUE + 1 is negative. Where did the value go?
Nowhere — the arithmetic is exact modulo 2³². The carry lands in the sign bit,
so the same bit pattern that means "just over two billion" as unsigned means
"minus two billion" as signed. Java's ints are signed, so that is what you see.
Why is 30 * 24 * 60 * 60 * 1000 wrong but 30L * 24 * 60 * 60 * 1000
right?
Each multiplication takes the wider of its two operand types. Starting with a
long makes every subsequent product a long, so nothing overflows. Putting
the L at the end is too late: the int product has already wrapped and the
widening preserves the wrong value.
Math.abs(hashCode()) % buckets produced a negative index. Explain and fix.
The hash was Integer.MIN_VALUE, which abs returns unchanged because its
positive counterpart is not representable; % then yielded a negative
remainder. Use Math.floorMod(hashCode(), buckets), which is non-negative for
any input.
Where this question goes next
Every runnable example above was compiled and executed against openjdk 21.0.11 on this build, and its output diffed against what this page claims. Last updated 2026-09-11.