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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Model money with BigDecimal and explicit rounding, never floating-point, to avoid silent precision errors.
- 2Immutable value objects that return new instances make arithmetic predictable and thread-safe.
- 3Splitting money requires redistributing leftover pennies so the parts sum exactly back to the whole.
Related explainers
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
java
package com.acme.billing.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties;
Feature-flagged beans with Spring @ConditionalOnProperty
feature-flags
conditional-beans
strategy-pattern
Intermediate
5 steps
java
public static Map<String, String> parseCookieHeader(String header) { Map<String, String> cookies = new LinkedHashMap<>(); if (header == null || header.isBlank()) { return cookies;
Parsing an HTTP Cookie header in Java
string-parsing
http
url-decoding
Intermediate
6 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
java
public class TimedSocketReader { private static final int READ_TIMEOUT_MS = 5_000; private static final int CONNECT_TIMEOUT_MS = 3_000;
Reading a socket with connect and read timeouts
sockets
timeouts
io
Intermediate
8 steps
java
public final class EncodingDetector { public enum Encoding { UTF_8, UTF_16LE, UTF_16BE, UTF_32LE, UTF_32BE, ASCII, UNKNOWN
Detecting text encoding from raw bytes in Java
byte-manipulation
encoding-detection
bitwise-operations
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-safe-money-value-type-in-java-explained-java-1bf5/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.