Hidde Wieringa
Posted on April 17, 2021
The JavaMoney API is part of JSR 354 and will (hopefully) be part of the Java language in the future. The reference implementation Moneta provides good examples of implementations using the JavaMoney API.
The JavaMoney API allows working with currencies, monetary values and conversion between monetary values of different currencies through exchange rates. View the Devoxx video which explains the API in depth.
Kotlin is interoperable with compiled Java libraries. So the JavaMoney/Moneta library can be used from Kotlin code, and makes working with money in business logic easy. After all, money seems trivial but details like rounding, conversion and catalogues of locales and currencies are hard to implement correctly.
The JavaMoney API is very flexible and extendable. The classes and interfaces provide a Builder pattern to contruct instances with fluent code. For example
// Construct a monetary value of €200 using the FastMoney implementation
Monetary.getAmountFactory(FastMoney.class)
.setCurrencyUnit("EUR")
.setNumber(200)
.setContext(MonetaryContextBuilder.of().set(MathContext.DECIMAL128).build())
.create();
The builder pattern does not look like ideomatic Kotlin. In Kotlin, builder DSLs are used instead to fluently construct complex objects.
That is exactly what I implemented in Money-Kotlin.
Money-Kotlin
The Money-Kotlin library extends the JavaMoney API with Kotlin extension functions.
The example above could be rewritten in Kotlin as
// Construct a monetary value of €200 using the FastMoney implementation
200.ofCurrency<FastMoney>("EUR", monetaryContext {
set(MathContext.DECIMAL128)
})
More details are given below. Some of the (Java) examples are taken from the Moneta User Guide.
Monetary amounts
The first example displayed above shows how to construct monetary values. Often we do not need a monetary context.
Java:
Monetary.getAmountFactory(FastMoney.class)
.setCurrencyUnit("EUR")
.setNumber(200.01)
.create();
Kotlin:
val money = (200.01).ofCurrency<FastMoney>("EUR")
We can manipulate money amounts as well, just like regular numbers. This has been implemented using Kotlin operator overloading.
Java:
MonetaryAmount money = ...;
// add
money.add(money);
money.plus();
// subtract & negate
money.subtract(money);
money.negate();
// multiply
money.multiply(2.0);
// divide & remainder
money.divide(2.0);
money.remainder(2.0);
Kotlin:
// add
money + money
+money
// subtract & negate
money - money
-money
// multiply
money * 2.0
2.0 * money
// divide & remainder
money / 2.0
money % 2.0
Currencies
You can access a default or self-implemented currency (like Bitcoin) from the Monetary
singleton.
Java:
CurrencyUnit currencyEUR = Monetary.getCurrency("EUR");
Kotlin:
"EUR".asCurrency()
Locales
The JavaMoney API associates a locale with a currency. For example, in The Netherlands the official currency is the Euro. Some countries have multiple currencies.
Java:
new Locale("", "NL");
Monetary.getCurrency(new Locale("", "NL"));
Monetary.getCurrencies(new Locale("", "NL"));
Kotlin:
"NL".asCountryLocale()
"NL".asCountryLocale().getCurrency()
"NL".asCountryLocale().getCurrencies()
Rounding
A monetary value can be rounded in a specific way. Construct a rounding query to get a MonetaryRounding
.
Java:
Monetary.getRounding(
RoundingQueryBuilder
.of()
.setScale(4)
.set(RoundingMode.HALF_UP)
.build()
);
Kotlin:
Monetary.getRounding(roundingQuery {
setScale(4)
set(RoundingMode.HALF_UP)
})
Currency conversion
A MonetaryAmount
can be converted from one currency to another currency through a CurrencyConversion
. A CurrencyConversion
depends on a conversion provider. There are some default providers implemented out of the box, like the European Central Bank (ECB) and International Monetary Fund (IMF). These providers provide currency exchange rates, in particular historic rates.
The order of the default providers are configurable, and you can implement your own conversion provider as well.
Java:
ExchangeRateProvider rateProvider = MonetaryConversions.getExchangeRateProvider("ECB", "IMF");
MonetaryAmount amountInEUR = ...;
MonetaryAmount amountInCHF = amountInEUR.with(rateProvider.getExchangeRate("EUR", "CHF"));
// Using the default rate providers
MonetaryAmount amountInCHF = amountInEUR.with(MonetaryConversions.getExchangeRateProvider().getExchangeRate("EUR", "CHF"));
Kotlin:
val rateProvider = MonetaryConversions.getExchangeRateProvider("ECB", "IMF");
val amountInEUR = ...
val amountInCHF = amountInEUR.convertTo("CHF", rateProvider)
// Using the default rate providers
val amountInCHF = amountInEUR.convertTo("CHF")
Formatting
Monetary amounts are formatted in the context of a locale.
Java:
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.GERMANY);
MonetaryAmount amount = ...;
format.format(amount);
Kotlin:
val amount = ...
val format = Locale.GERMANY.monetaryAmountFormat()
format.format(amount);
Getting started
You can use the Money-Kotlin library today! It has been published on Maven Central, and can be added as a dependency.
There are many more examples in the project readme.
Gradle:
implementation("nl.hiddewieringa:money-kotlin:$moneyKotlinVersion")
Maven:
<dependency>
<groupId>nl.hiddewieringa</groupId>
<artifactId>money-kotlin</artifactId>
<version>${moneyKotlin.version}</version>
</dependency>
Feedback, feature requests and bug reports are welcome!
Posted on April 17, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.