java 64 lines · 10 steps

A safe Money value type in Java

An immutable Money class uses BigDecimal and currency-aware rounding to handle amounts without losing pennies.

Explained by highlit
1import java.math.BigDecimal;
2import java.math.RoundingMode;
3import java.util.Currency;
4 
5public final class Money {
6 
7 private final BigDecimal amount;
8 private final Currency currency;
9 
10 private Money(BigDecimal amount, Currency currency) {
11 this.currency = currency;
12 this.amount = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_EVEN);
13 }
14 
15 public static Money of(String amount, String currencyCode) {
16 return new Money(new BigDecimal(amount), Currency.getInstance(currencyCode));
17 }
18 
19 public Money plus(Money other) {
20 requireSameCurrency(other);
21 return new Money(amount.add(other.amount), currency);
22 }
23 
24 public Money multipliedBy(BigDecimal factor) {
25 return new Money(amount.multiply(factor), currency);
26 }
27 
28 public Money applyTaxRate(BigDecimal ratePercent) {
29 BigDecimal tax = amount.multiply(ratePercent).divide(new BigDecimal("100"), 10, RoundingMode.HALF_UP);
30 return new Money(amount.add(tax), currency);
31 }
32 
33 public Money[] allocate(int parts) {
34 BigDecimal share = amount.divideToIntegralValue(new BigDecimal(parts))
35 .setScale(currency.getDefaultFractionDigits(), RoundingMode.DOWN);
36 Money[] result = new Money[parts];
37 Money remainder = new Money(amount, currency);
38 for (int i = 0; i < parts; i++) {
39 result[i] = new Money(share, currency);
40 remainder = remainder.minus(result[i]);
41 }
42 BigDecimal penny = BigDecimal.ONE.movePointLeft(currency.getDefaultFractionDigits());
43 for (int i = 0; remainder.amount.signum() > 0; i++) {
44 result[i] = new Money(result[i].amount.add(penny), currency);
45 remainder = new Money(remainder.amount.subtract(penny), currency);
46 }
47 return result;
48 }
49 
50 private Money minus(Money other) {
51 return new Money(amount.subtract(other.amount), currency);
52 }
53 
54 private void requireSameCurrency(Money other) {
55 if (!currency.equals(other.currency)) {
56 throw new IllegalArgumentException("Currency mismatch: " + currency + " vs " + other.currency);
57 }
58 }
59 
60 @Override
61 public String toString() {
62 return currency.getSymbol() + amount.toPlainString();
63 }
64}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Model money with BigDecimal and explicit rounding, never floating-point, to avoid silent precision errors.
  2. 2Immutable value objects that return new instances make arithmetic predictable and thread-safe.
  3. 3Splitting money requires redistributing leftover pennies so the parts sum exactly back to the whole.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A safe Money value type in Java — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code