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.

Syntaxint 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 * 1024 overflows at 2048 MB, and 4096 MB gives exactly 0.
  • Counters and totals: summing order amounts in cents or counting events in an int.
  • Averages: (low + high) / 2 overflows 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.floorDiv rounds toward negative infinity, unlike /, and Math.floorMod takes 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.round and Math.pow are the everyday ones; pow works in double.

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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · java
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
// 1750000000

Size 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.

Example · java
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
// 4294967296

When 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.

Example · java
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

  • Be the first to comment on this lesson.