Math.addExact computes an int or long sum and throws ArithmeticException instead of silently wrapping an out-of-range result.
Java checked integer arithmetic: reject overflow before updating state
Calculate before publishing
A counter near Integer.MAX_VALUE can wrap negative with ordinary addition. In inventory or billing state, that is not a harmless numerical oddity: later bounds checks may accept a wrong value. Compute the candidate with addExact first, then assign it to the live counter. The program reaches the int maximum exactly and rejects one more unit without changing the stored count.
A wider long delays overflow but does not remove it. For unbounded mathematical totals, choose BigInteger; for decimal amounts, use an explicit BigDecimal policy. Numeric promotion matters before the checked call: an overflowing expression inside an argument may already be wrong by the time addExact sees it.
Make failure part of the API
This small counter rejects a negative increment and exposes a read method. A real shared counter also needs synchronization or an atomic state transition; addExact alone does not make a read-modify-write thread-safe. Atomic variables and locks cover that separate problem.
Multiplication, subtraction, narrowing to int, and increment have corresponding exact methods. Use the operation matching the domain rule rather than testing after a wrapped value has escaped into another object.
Working program
public class CheckedStockCounter {
static final class StockCounter {
private int received;
StockCounter(int received) { this.received = received; }
void add(int units) {
if (units < 0) throw new IllegalArgumentException("negative units");
int candidate = Math.addExact(received, units);
received = candidate;
}
int received() { return received; }
}
public static void main(String[] args) {
StockCounter counter = new StockCounter(2_147_483_600);
counter.add(47);
System.out.println(counter.received());
try { counter.add(1); }
catch (ArithmeticException rejected) { System.out.println("overflow rejected"); }
System.out.println(counter.received());
}
}Output
2147483647
overflow rejected
2147483647Cost and ownership
Each checked primitive operation has O(1) time and storage cost. This example protects arithmetic correctness for one thread; a concurrent counter requires an additional atomicity boundary around the read, calculation, and write.
Common Mistakes
- Do not check an int after it has already wrapped and call that overflow protection.
- Do not assume long is mathematically unbounded.
- Do not confuse exact arithmetic with concurrent atomic updates.
Read next
Java operators and numeric promotion, Java atomic variables: compare-and-set and one-variable invariants, Java ReentrantLock: protect a complete state transition, Java BigDecimal: decimal amounts and explicit rounding.
More numeric representation boundaries
Continue with Java Math.abs on Integer.MIN_VALUE: detect the one value that cannot turn positive, Java BigInteger.intValueExact: reject a narrowing conversion, Java long to double: large integer IDs can lose unit precision.
