DEV Community

Hidde Wieringa
Hidde Wieringa

Posted on

(Java)Money and Kotlin

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();
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

Kotlin:

val money = (200.01).ofCurrency<FastMoney>("EUR")
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Currencies

You can access a default or self-implemented currency (like Bitcoin) from the Monetary singleton.

Java:

CurrencyUnit currencyEUR = Monetary.getCurrency("EUR");
Enter fullscreen mode Exit fullscreen mode

Kotlin:

"EUR".asCurrency()
Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

Kotlin:

"NL".asCountryLocale()
"NL".asCountryLocale().getCurrency()
"NL".asCountryLocale().getCurrencies()
Enter fullscreen mode Exit fullscreen mode

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()
    );
Enter fullscreen mode Exit fullscreen mode

Kotlin:

Monetary.getRounding(roundingQuery {
    setScale(4)
    set(RoundingMode.HALF_UP)
})
Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

Formatting

Monetary amounts are formatted in the context of a locale.

Java:

MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.GERMANY);
MonetaryAmount amount = ...;
format.format(amount);
Enter fullscreen mode Exit fullscreen mode

Kotlin:

val amount = ...
val format = Locale.GERMANY.monetaryAmountFormat()
format.format(amount);
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

Maven:

<dependency>
  <groupId>nl.hiddewieringa</groupId>
  <artifactId>money-kotlin</artifactId>
  <version>${moneyKotlin.version}</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Feedback, feature requests and bug reports are welcome!

Top comments (0)