🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOGjava.lang.ArithmeticException: Rounding necessary at java.base/java.math.BigDecimal.commonNeedIncrement(BigDecimal.java:4882) at java.base/java.math.BigDecimal.needIncrement(BigDecimal.java:4932) at java.base/java.math.BigDecimal.divideAndRound(BigDecimal.java:4846) at java.base/java.math.BigDecimal.setScale(BigDecimal.java:2900) at java.base/java.math.BigDecimal.setScale(BigDecimal.java:2960) at com.devinhyderabad.invoice.MoneyFormatter.cents(MoneyFormatter.java:30)

⚡ Quick Fix Works 80% of the time

Pass an explicit rounding mode to setScale.

BigDecimal price = raw.setScale(2, RoundingMode.HALF_UP);

🧠 Why this Happens

Tap to expand the deep technical explanation

One-arg setScale(int) delegates to the two-arg overload with RoundingMode.UNNECESSARY - a CONTRACT, not a default: the caller asserts no information will be lost. Reducing scale requires dividing by a power of ten, which is why the refusal genuinely travels through divideAndRound despite the misleading name (verified against Temurin 25: the captured frames ARE the setScale path). commonNeedIncrement fires "Rounding necessary" when discarded digits are non-zero, because honoring both the new scale and exactness is impossible. Also verified: intValueExact()/longValueExact() route through this very setScale chain, so the same exception backs the *Exact family - their extra trick is throwing a distinct overflow message when the magnitude does not fit.

The HITEC City Parking Spot Analogy:

Signing a form that promises "no cents were dropped" while your amount still holds a half-cent: the notary stops you at the desk.

🔁 How to Reproduce Confirm this is your error

new BigDecimal("1.234").setScale(2); // ArithmeticException: Rounding necessary

🛠️ Solutions (5 Ways to Fix)

Solution 1✓ Most common cause

Provide the intended RoundingMode explicitly

👉 Use this whenever reducing scale for display or storage.

Naming HALF_UP (or your policy) replaces the accidental UNNECESSARY assertion with deliberate, documented behavior.

BigDecimal net = gross.setScale(2, RoundingMode.HALF_UP);
Solution 2

Use HALF_EVEN for financial aggregates

👉 Use this if many rounded values get summed later.

Banker rounding eliminates upward bias across large datasets; it is the accounting-standard choice for ledgers and tax splits.

total = line.setScale(2, RoundingMode.HALF_EVEN).add(total);
Solution 3

Reserve *Exact() methods for genuinely exact flows

👉 Use this if converting stored decimals to primitives.

intValueExact()/longValueExact() are safety tools that PROVE no fraction/overflow exists; if they can throw in normal operation, use plain intValue plus range checks instead.

int units = quantity.intValueExact(); // only after validating whole numbers
Solution 4

Separate storage precision from display formatting

👉 Use this if UI needs fewer decimals than the database stores.

Keep full precision on entities; format at presentation via NumberFormat/String so setScale calls (and their exceptions) vanish from domain logic.

NumberFormat money = NumberFormat.getCurrencyInstance(); label.setText(money.format(fullPrecisionValue));
Solution 5

Flag mode-less BigDecimal calls with IDE inspection gates

👉 Use this if the team wants every divide()/setScale() without an explicit RoundingMode caught before merge.

IntelliJ's BigDecimalMethodWithoutRoundingCalled inspection is type-aware, so it catches setScale(scale), setScale(10) and setScale(SCALE) alike - exactly the calls a text grep misses - and runs headless in CI via inspect.sh or Qodana. SpotBugs users get equivalent coverage from the inspequte plugin rule BIGDECIMAL_SET_SCALE_WITHOUT_ROUNDING.

# CI gate: IntelliJ inspections, headless (Qodana-compatible) $IDEA_HOME/bin/inspect.sh <project> <profile> <outDir> -v2 # Inspection ID: BigDecimalMethodWithoutRoundingCalled # reports divide()/setScale() lacking a rounding-mode argument. # Provably-exact call sites opt out explicitly: # //noinspection BigDecimalMethodWithoutRoundingCalled

📋 Version Notes

Java 5+

"Rounding necessary" wording stable since RoundingMode introduction.

Java 8+

Unchanged. stripTrailingZeros() fixed for whole numbers like 100.00 around Java 8 - older JDKs left trailing zeros.

🛡️ How to Prevent This Next Time

Every scale change states its rounding mode. Treat UNNECESSARY as an assertion tool for provably-exact paths only.