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

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.

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