Practical Money in Java
Introduction
While investigating Protobuf I shot from the hip and wrote that: to model money, one should use BigDecimal. That’s the conventional wisdom and it is correct - in a lot of cases. It’s fine when you deal with one single currency and you are most of all concerned with the precision of financial calculations. But what are some other options? let’s find out.
For this exploration, I’ve defined some scenarios to see how the different options stack up to each other.
Scenarios
- 1 - Single currency and maybe some calculations
-
Lots of systems are in this realm; in this scenario the currency is a fixed one (Euro, Dollar or any local currency) and there are none or hardly any calculations.
- 2 - More than one currency but no calculations
-
A little bit more tricky, an amount now consists of the value and an accompanying currency. This could be your typical Web backend, interfacing between company systems and the Web frontend. Or it could be a webshop application that supports multiple currencies (one at the time) but gets its prices with currencies from another system.
- 3 - More than one currency and calculations
-
This is an extension of the previous scenario, as it adds precision calculations. This could be a financial system that does complex calculations, for example a mortgage quote application, and that needs to support amounts in different currencies but only supports one at the time.
- 4 - Conversions between currencies
-
Building on scenario 3, it adds the pesky problem of how to do rate conversions. Conversions of rates are difficult because you cannot always use the current rate, sometimes historical rates are needed. And maybe you need to remember the rate you used and on which date. This scenario can get complicated very fast and the more support the better.
Viable options
Let’s look at our options and see how they stack up.
Float & Double
First, never, ever use Float
to represent currency amounts. It is just not precise enough. The following code will start to fail
at higher numbers:
for (long l = 100; l < 80000000; l++) {
// convert cents-as-long to currency format
String ls= String.format("%s.%02d", l / 100 , l % 100);
// convert string to float to currency
var fs = String.format("%2.2f", Float.parseFloat(ls));
if (!fs.equals(ls)) {
System.out.println("Fail: float=" + fs + ", long=" + ls);
}
}
With Double
, your miles may vary. It has the same instability of a Float
but it will only start to show on very large numbers; or annoyingly quick when doing calculations.
Quick demonstration, you better not round down in your formatting:
System.out.println(9876543210.0d +0.08d - 9876543210.0d);
// Output: 0.07999992370605469
For scenario 1 and 2 it’s usable when you never need to do calculations.
But be aware, you’re on thin ice, and the way this comes back to you will be user issues complaining about incorrect cents in amounts.
Looking at the scenarios, for scenarios 1 and 2 it’s usable, for scenario 3 and 4 you probably are creating some kind of financial application with calculations which does not work good enough with a Double
.
Long
Without the need for precise calculations, this can be a very easy solution to represent the value part of a currency amount. As long as you are prepared to store the value as cents. Sidenote: A 'Long' can contain the US national debt in cents close to 4000 times, so no worries there…
It will only work in the scenarios without calculation because you simply cannot be precise with a Long
.
BigDecimal
This is the goto solution for currency values. It is precise and exact and can understand the complicated rounding strategies needed
for financial calculations[1].
With a BigDecimal
, we’ll never lose those rounding cents anymore.
So far the good news but BigDecimal
is a general purpose type and it can take some getting used to with the scale, precision and rounding settings.
A BigDecimal
is well suited to all scenarios, with the caveat that it is quirky and to properly model currency you need 2 fields,
one for the value and one for the currencycode.
For scenario 1 including complex calculations it’s a good fit. For scenario 2 it’s about as viable as a Long
.
When support for more currencies will be needed as in scenarios 3 and 4, it quickly becomes a hassle because you need to constantly use 2 fields for the same logical field.
DIY modelling
Every application is different and if the need arises the biggest drawbacks of the options above can be encapsulated into a single bespoke class[2].
It’s a lot of work to get it right but things like precise calculations, formatting (with locale),
combining the value and code fields and more are easily brought under control.
If you need the extreme amount of control it gives you and are fine with having a bespoke Money solution, you probably already have chosen for this option.
If not, you might want to look at the other options first.
That said, it will be a perfect fit for all scenarios because, well it’s our code so it must be perfect.
Joda Money
Not an option that is well-known but just as the famous cousin joda-time it is a well-thought-out light-weight library[3]
that basically is a generic version of the previous option.
It offers all the goodies that come with modelling Money without the effort to craft your own solution.
Internally it uses a BigDecimal
to capture the value and does its best to keep all the nitty-gritty of BigDecimal
away while still being flexible enough to support complicated financial calculations.
So for all scenarios this is a good fit, if you know you can stay within the limitations of the library. With regard to the last scenario, it also has some support for rate calculations but it is not extensive.
Sample code, from the Joda Money site:
// create a monetary value
Money money = Money.parse("USD 23.87");
// add another amount with safe double conversion
CurrencyUnit usd = CurrencyUnit.of("USD");
money = money.plus(Money.of(usd, 12.43d));
// multiplies by 3.5 with rounding
money = money.multipliedBy(3.5d, RoundingMode.DOWN);
// compare two amounts
boolean bigAmount = money.isGreaterThan(dailyWage);
// convert to GBP using a supplied rate
BigDecimal conversionRate = ...; // obtained from code outside Joda-Money
Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);
JSR 354: Money and Currency API
This[4] is the elephant in the room as the JSR was scheduled to go into Java 9,
was postponed, spliced off and is now a separate library. It probably will never be added to Java SE and who maintains it is not clear after the original backers stopped.
That being out of the way, it does look like a very fine, extensive and somewhat complicated library.+
Where Joda Money makes some works-for-almost-all decisions like always using BigDecimal, have somewhat limited formatting and limited conversion support,
the JavaMoney API does it all.+
The reference implementation Moneta[5] has a datatype Money with a BigDecimal
backing it and a FastMoney that is backed by a Long
which is about twice as fast as the BigDecimal
one.
The latter one should not be used for calculations.
Moneta also has thorough support for currency conversion with connections to current and historical exchange rate providers like the ECB and IMF.
So if you work at a bank and need everything including the kitchensink, have a good look at JavaMoney because that’s probably the library you need. For the rest of us it might be overkill, but who knows, you might need it later?
It’s reference implementation does cover all the scenarios without breaking a sweat. But be prepared to invest time to get it to work.
Some sample code:
// Creating instances of Money:
Money m1 = Monetary.getAmountFactory(Money.class).setCurrency("EUR").setNumber(25.25).create();
Money m2 = Money.of(25.25, "EUR");
Money m3 = Money.parse("EUR 25.25");
// Get a formatter for dutch money layout
MonetaryAmountFormat formatForNL = MonetaryFormats.getAmountFormat(Locale.forLanguageTag("nl-NL"));
ExchangeRateProvider rateProvider = MonetaryConversions.getExchangeRateProvider( "IMF");
CurrencyConversion conversionToEUR = rateProvider.getCurrencyConversion("EUR");
// Convert some money
Money amountInUSD = Money.of(300.00, "USD");
// From USD to EUR with today's rate
var convertedToEUR1 = amountInUSD.with(conversionToEUR);
System.out.println("300.00 USD converted to EUR = "+ formatForNL.format(convertedToEUR1));
Money amountInEUR = Money.of(300.00, "EUR");
// Output: 300.00 USD converted to EUR = EUR 256,43
// From EUR to EUR with today's rate, should always be 1-on-1
var convertedToEUR2 = amountInEUR.with(conversionToEUR);
System.out.println("300.00 EUR converted to EUR = "+ formatForNL.format(convertedToEUR2));
// Output: 300.00 EUR converted to EUR = EUR 300,00
Other concerns
As you might know Java is not an island; we usually need to connect to other systems and probably to frontends.
In the browser Javascript has most of the same[6] problems and libraries to support.
With regard to the JSON world, you probably have to fall back to putting the money value and the currency in separate fields with the value as a string (with a decimal point), so it can be parsed into
whatever the target platform is comfortable with.
Conclusion
Most important consideration on how to deal with money in your system is understanding in which scenario you fall, not only now but also in the foreseeable future.
If you can get away with always using a Long
because you are firmly in the first scenario with only data entry and it fits your needs, go for it.
But if you’re an important financial system with lots of calculations including rate conversions you’re probably stuck with
looking at JavaMoney or even Do-It_Yourself.
For everything in between, you now have more understanding of the problem area.