Integer Overflow and Math Methods
See how int and long wrap around silently, and use Math.addExact, toIntExact, floorDiv, clamp and BigInteger to get correct or failing results.
int sum = Math.addExact(a, b); // throws ArithmeticException on overflow
long bytes = Math.multiplyExact(mb, 1_048_576L);
int n = Math.toIntExact(someLong);
int size = Math.clamp(requested, 1, 100);Java integer arithmetic does not check for overflow. When the result of an int operation is larger than Integer.MAX_VALUE (2,147,483,647), it wraps around to the negative end and keeps going. No exception, no warning. Integer.MAX_VALUE + 1 is -2147483648. long does the same at about 9.2 quintillion.
Where it bites in back-end code
- Time: thirty days in milliseconds does not fit in an
int. - Sizes:
megabytes * 1024 * 1024overflows at 2048 MB, and 4096 MB gives exactly0. - Counters and totals: summing order amounts in cents or counting events in an
int. - Averages:
(low + high) / 2overflows when both are large, the bug that sat in many binary search implementations for years.
The result is usually a negative number where none should exist: a negative quota, a cache entry that expired in 1969, a Content-Length that makes no sense.
Fail loudly with the exact methods
Math.addExact, subtractExact, multiplyExact, incrementExact, negateExact and toIntExact do the same arithmetic but throw ArithmeticException on overflow. They have int and long versions. Use them where a wrong number would be worse than a failed request: money, quotas, sizes from untrusted input. A clear exception becomes a 400 or a 500 with a log line; a silent wrap becomes corrupted data.
Other Math methods worth knowing
Math.abs(Integer.MIN_VALUE)is still negative, because +2,147,483,648 does not fit.Math.absExact(Java 15) throws instead.Math.floorDivrounds toward negative infinity, unlike/, andMath.floorModtakes the sign of the divisor, unlike%.Math.ceilDiv(Java 18) gives ceiling division for page counts.Math.clamp(value, min, max)(Java 21) keeps a value within bounds, which suits page sizes and retry delays.Math.max,Math.min,Math.roundandMath.poware the everyday ones;powworks indouble.
When the number is genuinely large
If values can exceed long, use java.math.BigInteger, which grows as needed and has no practical upper limit. For money with fractions, use BigDecimal. Both are slower than primitives, so keep them for the values that need them.
Prevention is cheap: choose long for anything that grows over time, put an L on the first literal of a time or size calculation, and validate input ranges before doing arithmetic with them.
Example
When to use it
- Reject an upload quota or Content-Length that would overflow instead of storing a wrapped value.
- Clamp a requested page size or retry delay into an allowed range with Math.clamp.
- Add up order totals in cents with Math.addExact so an overflow fails the request instead of corrupting a report.
More examples
The midpoint bug
The sum 3.5 billion does not fit in an int, so the average comes out negative. Subtracting first, or an unsigned shift, avoids the overflow for non-negative inputs.
void main() {
int low = 1_500_000_000;
int high = 2_000_000_000;
IO.println((low + high) / 2); // the sum overflows first
IO.println(low + (high - low) / 2); // never exceeds high
IO.println((low + high) >>> 1); // treats the wrapped sum as unsigned
}
// Output:
// -397483648
// 1750000000
// 1750000000Size calculations with multiplyExact
A 4 GB quota computed in int becomes 0, which a storage check would happily accept. multiplyExact refuses the int version and gives the right answer in the long version.
void main() {
int quotaMb = 4096;
int wrong = quotaMb * 1024 * 1024; // 2^32 wraps to exactly 0
long right = quotaMb * 1024L * 1024; // long from the first multiplication
IO.println(wrong + " " + right);
try {
int bytes = Math.multiplyExact(quotaMb, 1024 * 1024);
IO.println(bytes);
} catch (ArithmeticException e) {
IO.println("rejected: " + e.getMessage());
}
IO.println(Math.multiplyExact((long) quotaMb, 1_048_576L));
}
// Output:
// 0 4294967296
// rejected: integer overflow
// 4294967296When long is not enough: BigInteger
The long version wraps to a negative number with no error, while BigInteger grows as needed. BigInteger objects are immutable, so each multiply returns a new value that must be reassigned.
import java.math.BigInteger;
void main() {
long f = 1;
for (int i = 2; i <= 21; i++) {
f *= i; // 21! does not fit in a long
}
IO.println("long 21! = " + f);
BigInteger big = BigInteger.ONE;
for (int i = 2; i <= 21; i++) {
big = big.multiply(BigInteger.valueOf(i));
}
IO.println("BigInteger 21! = " + big);
IO.println(big.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0);
}
// Output:
// long 21! = -4249290049419214848
// BigInteger 21! = 51090942171709440000
// true
Discussion