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
@AutoConfiguration @EnableConfigurationProperties(RateLimiterProperties.class) @ConditionalOnClass(RateLimiter.class) public class RateLimiterAutoConfiguration {
Building a Spring Boot rate-limiter auto-config
auto-configuration
conditional-beans
rate-limiting
Intermediate
7 steps
java
public class RequestCoalescer<K, V> { private final ConcurrentHashMap<K, CompletableFuture<V>> inFlight = new ConcurrentHashMap<>(); private final Function<K, V> loader;
Coalescing duplicate requests in Java
concurrency
caching
completablefuture
Advanced
6 steps
java
public final class NaturalOrderComparator implements Comparator<String> { public static final NaturalOrderComparator INSTANCE = new NaturalOrderComparator();
Natural-order string sorting in Java
comparator
natural-sort
string-parsing
Intermediate
9 steps
python
from copy import deepcopy class CowSnapshot:
Copy-on-write attribute snapshots in Python
copy-on-write
descriptors
attribute-interception
Advanced
7 steps
java
package com.example.validation; import java.util.List; import java.util.stream.Collectors;
Validating JSON payloads against a schema in Java
json-schema
validation
recursion
Intermediate
8 steps
java
public URI buildSearchUri(String query, int page, int size, List<String> tags) { UriComponentsBuilder builder = UriComponentsBuilder .fromUriString("https://api.example.com") .path("/v2/products/search")
Building URIs safely with UriComponentsBuilder in Spring
url-building
builder-pattern
encoding
Intermediate
5 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.