ArithmeticException: Fix Java Divide-by-Zero
Fix ArithmeticException fast: guard integer divisors, handle doubles separately, and scale every BigDecimal divide with a rounding mode...
20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.
- ✓Basic Java arithmetic and types
- ✓BigDecimal basics
- ✓A JDK to compile examples
- ArithmeticException means integer division or remainder by zero — int and long only, never floating point
- Double division by zero yields Infinity or NaN instead of throwing, so check which type you're dividing
- Guard every integer divisor with a zero check or validation before the operation runs
- Give BigDecimal.divide an explicit scale and rounding mode or it throws on non-terminating results
Picture splitting a pizza among zero friends — the question makes no sense, and Java refuses to answer it. That's ArithmeticException: integer math hitting divide-by-zero and throwing up its hands. But decimals play by looser rules — double division by zero calmly answers Infinity instead of crashing. The fix is checking the guest count before cutting: guard integer divisors, know doubles won't throw, and give precise BigDecimal math its rounding instructions upfront.
The trace says ArithmeticException: / by zero at the line computing an average. The count came from a query that returned no rows, the total divided by that zero count, and the nightly job died. The values were all legitimate — an empty day is normal — but nobody guarded the division, so normal input became a fatal error.
This exception has the sharpest split personality in Java: integer division by zero throws, floating-point division by zero doesn't. Developers who learn one rule apply it to both and get burned — either missing guards on ints or writing dead guards on doubles. Add BigDecimal's divide, which throws for non-terminating decimals even with nonzero divisors, and the family needs a proper map.
This guide draws it. You'll learn the int-versus-double split, divisor guards that fit real code, BigDecimal scale and rounding, empty-dataset averages, and the overflow cousins that throw the same type. By the end, every division in your code carries the right protection for its type — and empty inputs produce defaults or messages, not pages.
Integers Throw, Doubles Don't: the Great Split
Memorize this table and half the confusion vanishes. int and long division or remainder by zero throws ArithmeticException — always, deterministically. float and double division by zero never throws: positive over zero gives Infinity, negative gives -Infinity, zero over zero gives NaN. Mixed expressions follow the wider type, so 7 / 0.0 is double division returning Infinity while 7 / 0 throws. The operator looks identical; the operand types decide everything.
This split bites both directions. Teams guard double divisions that can't throw — harmless dead code that signals misunderstanding. Worse, teams skip guards on integer math assuming doubles' leniency, or convert to double to dodge the throw and inherit Infinity downstream where a price becomes infinite and a comparison silently passes. Infinity propagates through calculations the way null propagates through calls — far and quietly.
The demo below prints every case side by side. Run it once, show it to every junior, and pin it in the team wiki. When reviewing division code, the first question is always the operand types — the answer dictates guard or no guard. When reviewing division code, the first question is always the operand types, because the answer dictates guard or no guard. Run the demo once and pin it in the team wiki for every junior to see.
Guarding Divisors in Real Code
Divisor guards belong where the divisor is born, not where the division happens. A count from a query, a size from a list, a user-supplied split count — validate at entry with a message naming the value and rule: split count must be positive, got 0. The division downstream then runs unconditionally on trusted data. Guards at the division site work but scatter; guards at the source centralize and document.
Choose the zero policy per meaning. Averages over empty data return Optional.empty, a no-data response, or a documented default — never a fabricated zero that looks like a real average. Pagination with zero page size throws a validation error because it's a caller bug. Rates with zero elapsed time skip the sample. The code review question is always what zero means here — and the answer differs per divisor.
The snippet shows the helper pattern: one guarded average used by every endpoint, so new callers inherit protection. Centralizing arithmetic with guards beats scattering ifs at every division site. When the next empty region launches, the helper answers gracefully while unguarded code would page. Centralizing arithmetic with guards beats scattering ifs at every division site, especially as new callers arrive. The code review question is always what zero means here, and the answer differs per divisor.
BigDecimal Divide Needs Scale and Rounding
BigDecimal's exact divide() throws ArithmeticException on non-terminating results — 1 divided by 3 has no exact decimal form, so exactness is impossible and the method says so loudly. This surprises developers who chose BigDecimal for safety and meet a throw with nonzero divisors. The exception is correct: you asked for exact, exact doesn't exist. The repair is stating acceptable precision: divide(divisor, scale, roundingMode).
Always use the three-argument form for division in money code: amount.divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP) for cents with banker's expectations documented. Choose scale per domain — 2 for currency display, higher for intermediate rates — and HALF_UP unless regulations say otherwise. Construct from strings or valueOf, never from doubles, or binary noise enters before division even runs.
The snippet contrasts the throw with the scaled cure plus the double-constructor trap. Money code review checks three things: string or valueOf construction, three-argument divide, documented rounding. Miss any and the change doesn't merge — precision bugs cost real cents at scale. Money code review checks three things: string construction, three-argument divide, and documented rounding on every call. Miss any of them and the change should not merge, since precision bugs cost real cents.
divide() without scale demands exactness, and 1/3 has no exact decimal form. Use the three-argument divide with scale and RoundingMode in all money code — no exceptions, literally.divide() calls and fix each one.Averages, Percentages, and Empty Datasets
Empty inputs are the top production source: zero rows, zero users, zero elapsed milliseconds. Every formula dividing by a count needs its empty path written before launch — not discovered at launch. Revenue per user with no users, error rate with no requests, velocity with no completed points: each is a division whose divisor starts at zero on day one for somebody. New regions, new tenants, and quiet days deliver the zero on schedule.
Percentages hide a second trap: integer percentage math truncates before it divides. done 100 / total works in int when ordered so multiplication precedes division; done / total 100 truncates to zero first for any partial progress. Order operations to preserve precision, or compute in double deliberately. Pagination's ceiling formula (items + size - 1) / size needs the guarded size from section two — its divisor is user-controlled.
The snippet shows empty-safe rate helpers returning honest optionals plus truncation-safe percentage order. Dashboards should distinguish no-data from zero distinctly — a gap versus a flatline tells different stories. Build the distinction into the helper and every chart inherits it. Build the no-data distinction into the helper and every chart inherits honest gaps instead of fake zeros. New regions, new tenants, and quiet days deliver the zero on schedule, so design for it.
Remainder, Overflow Cousins, and addExact
The % operator throws on zero exactly like division — 7 % 0 is ArithmeticException, not some gentler error. Guard remainders with the same divisor checks; cyclic math with a period that can be zero (empty rotation, unconfigured interval) needs the same empty-path treatment as averages. Reviewers should read % as / for guard purposes — same family, same rule.
Silent integer overflow is the adjacent trap that doesn't throw: 2_000_000_000 + 1_500_000_000 wraps negative with no complaint, corrupting totals that later divide into nonsense. The Math.*Exact family — addExact, subtractExact, multiplyExact, incrementExact, decrementExact, negateExact — throws ArithmeticException on overflow instead of wrapping. Use them for money totals, counters near limits, and any arithmetic whose overflow would corrupt downstream division.
The snippet shows the remainder guard plus exact-arithmetic detection. Exact methods convert silent corruption into loud failure at the overflow line — the same philosophy as divisor guards. For values that legitimately exceed int range, widen to long deliberately rather than catching overflow as control flow. For values that legitimately exceed int range, widen to long deliberately rather than catching overflow as control flow. Exact methods convert silent corruption into loud failure at the exact overflow line.
Reading / by zero and Locking the Fix
The message is terse — / by zero — and the line supplies the rest. Open it, identify the divisor operand, and trace that operand to its producer: a query count, a collection size, a config value, an elapsed-time subtraction. The divisor's story is the bug; the division is just where the story ends. Zero elapsed time from identical timestamps, zero counts from empty results, and zero sizes from fresh tenants cover nearly every production case.
Reproduce with the zero directly — call the method with count zero, run the query against the empty partition, set the clock source identical. If the code handles it gracefully after your fix, the repro becomes the regression test: empty input in, honest empty answer out, no throw. Name the test for the zero-state so history is visible: averageOfNoRowsReturnsEmpty.
Lock the family, not just the instance: grep every / and % on external counts across the service and route them through guarded helpers. One fixed endpoint while five siblings divide raw counts is half an incident waiting. Centralize, test empties per helper, and empty inputs stop paging forever. Centralize, test empties per helper, and empty inputs stop paging forever across the whole service. Fix the family with helpers rather than patching one line with a lone if.
Zero-Row Query Crashed Pricing for 35 Minutes
- Empty datasets are normal inputs, not edge cases. Every division needs a zero-count path — default, message, or fallback — designed before launch.
- New regions, tenants, and days start empty. Launch tests must cover the zero-state, not just the populated happy path.
- Reproduce with the failing data before rolling back. The data pointed at the guard in minutes; the rollback instinct would have hidden the lesson.
| File | Command / Code | Purpose |
|---|---|---|
| io | public final class DivSplit { | Integers Throw, Doubles Don't |
| io | public final class Averages { | Guarding Divisors in Real Code |
| io | public final class MoneyDivide { | BigDecimal Divide Needs Scale and Rounding |
| io | public final class Rates { | Averages, Percentages, and Empty Datasets |
| io | public final class RemainderExact { | Remainder, Overflow Cousins, and addExact |
Key takeaways
Common mistakes to avoid
6 patternsDividing raw query counts without guards
Guarding double divisions that can't throw
Converting to double to dodge the throw
Using two-argument BigDecimal divide
Truncating integer percentages
Ignoring silent int overflow
Interview Questions on This Topic
What throws ArithmeticException?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.
That's Exception Handling. Mark it forged?
5 min read · try the examples if you haven't