Formatting monetary amounts with Java Money

Java is very often used to develop big financial systems, yet still the JDK has little or no support for monetary arithmetics, currency conversion, formatting money for different locales etc. There’s only a Currency class which serves as a list of ISO 4217 currencies.

Fortunately we’ve got a JSR-354 standard and Moneta – the reference implementation. It provides us the Martin Fowler’s money pattern along with string formatting and currency repositories.

In this article I will focus on displaying monetary amounts for different currencies, locales and personal preferences.

Basic example: amount and ISO symbol

final Locale[] locales = {
    Locale.US,
    new Locale("nl", "NL"),
    new Locale("pl", "PL")
};

final Money amount = Money.of(12345.67, "USD");

for (Locale locale : locales) {
  MonetaryAmountFormat formatter = MonetaryFormats.getAmountFormat(locale);
  System.out.println(formatter.format(amount));
}
en_USnl_NLpl_PL
USD12,345.67USD 12.345,6712 345,67 USD

Amount with a currency symbol

final Locale[] locales = {
    Locale.US,
    new Locale("nl", "NL"),
    new Locale("pl", "PL")
};

final Money amount = Money.of(12345.67, "USD");

for (Locale locale : locales) {
  MonetaryAmountFormat formatter = MonetaryFormats.getAmountFormat(
      AmountFormatQueryBuilder.of(locale)
          .set(CurrencyStyle.SYMBOL)
          .build());
  System.out.println(formatter.format(amount));
}
en_USnl_NLpl_PL
$12,345.67USD 12.345,6712 345,67 USD

The $ currency symbol is displayed only for the United States locale. Other languages do not use it.

Amount with a full currency name

final Locale[] locales = {
    Locale.US,
    new Locale("nl", "NL"),
    new Locale("pl", "PL")
};

final Money amount = Money.of(12345.67, "USD");

for (Locale locale : locales) {
  MonetaryAmountFormat formatter = MonetaryFormats.getAmountFormat(
      AmountFormatQueryBuilder.of(locale)
          .set(CurrencyStyle.NAME)
          .build());
  System.out.println(formatter.format(amount));
}
en_USnl_NLpl_PL
US Dollar12,345.67US Dollar 12.345,6712 345,67 US Dollar

Amount without a thousands separator

final Locale[] locales = {
    Locale.US,
    new Locale("nl", "NL"),
    new Locale("pl", "PL")
};

final Money amount = Money.of(12345.67, "USD");

for (Locale locale : locales) {
  MonetaryAmountFormat formatter = MonetaryFormats.getAmountFormat(
      AmountFormatQueryBuilder.of(locale)
          .set(AmountFormatParams.GROUPING_SIZES, new int[]{2, 0})
          .build());
  System.out.println(formatter.format(amount));
}
en_USnl_NLpl_PL
USD12345.67USD 12345,6712345,67 USD

Grouping sizes are specified starting from the decimal point. In the above example we have two digits after the decimal point, and then we have no more grouping going to the left. Of course other combinations are possible, e.g. {2, 3, 4}.

Custom pattern

Using a pattern as defined by java.text.DecimalFormat.

final Locale[] locales = {
    Locale.US,
    new Locale("nl", "NL"),
    new Locale("pl", "PL")
};
final Money amount = Money.of(12345.67, "USD");
for (Locale locale : locales) {
  MonetaryAmountFormat formatter = MonetaryFormats.getAmountFormat(
      AmountFormatQueryBuilder.of(locale)
          .set(AmountFormatParams.PATTERN, "###,###.## ¤")
          .build());
  System.out.println(formatter.format(amount));
}
en_USnl_NLpl_PL
12,345.67 USD12.345,67 USD12 345,67 USD

Using your own currency in MoneyPHP

I have this talk about the MoneyPHP library and handling currencies in general. I often get a question: is it possible to define custom currencies in MoneyPHP? A possible use case would be loyalty cards for example, with their own scoring system.

The answer is: YES! MoneyPHP not only accepts all ISO 4217 currencies, but also allows you to define a custom list with any identifiers and any subunits. So for example, you can create a currency called BlahBlahBlah that has 7 decimal points and then convert an amount from your currency to another by providing a custom exchange list.

In the example below, we’re creating two fictious currencies: MyPoints and TheirPoints. The first one has two decimal points while another has none (only whole numbers). We create a Money object that holds exactly 123.45 MyPoints.

Then we specify an exchange table which says that 1 TheirPoint equals 0.5 MyPoints. We create a currency converter and then try to convert one currency to another. 123.45 * 0.5 equals 61.725. But since TheirPoints subunit is 0, the amount has to be rounded up, so we get 62.

use Money\Converter;
use Money\Currencies\CurrencyList;
use Money\Currency;
use Money\Exchange\FixedExchange;
use Money\Money;

$currencyList = new CurrencyList([
    'MyPoints' => 2,
    'TheirPoints' => 0,
]);

$myCardAmount = new Money(12345, new Currency('MyPoints'));

$exchange = new FixedExchange([
    'MyPoints' => [
        'TheirPoints' => 0.5,
    ],
]);
$converter = new Converter($currencyList, $exchange);

$theirCardAmount = $converter->convert(
    $myCardAmount, new Currency('TheirPoints')
);

echo $theirCardAmount->getAmount();  // 62

However, there can be a problem with currency formatting using PHP’s intl library. Consider this example:

$numberFormatter = new \NumberFormatter(
    'en_US', \NumberFormatter::CURRENCY
);
$moneyFormatter = new \Money\Formatter\IntlMoneyFormatter(
    $numberFormatter, $currencyList
);

echo $moneyFormatter->format($theirCardAmount);  // The 62.00

The odd output (The instead of Their and the unwanted fractional part) comes from the fact that PHP’s NumberFormatter handles only ISO currencies which have 3-character symbols. So you should use \NumberFormatter::DECIMAL instead and add your custom symbol manualy.

See my post about handling money in PHP

Learn how to count money… or you will lose it

Does anyone know where does the following difference come from?

$ php -r "var_dump((int) (4.20 * 100));"
int(420)

$ php -r "var_dump((int) (4.10 * 100));"
int(409)

It sounds weird when I get a ticket like this: When I set the price to $4.20 everything’s fine. But I cannot set the price to $4.10 because the system shows $4.09. I did some research and discovered that a user entered the price in dollars and then we converted it to cents to store in the database as an integer. That’s where the mistake was made.

Those problems arise from the way that CPU stores floating point numbers. PHP does not have a built-in decimal type, so it uses IEEE-754 standard. In this standard, numbers are stored in a binary system; both the mantissa and the exponent.

You can play with converting different numbers here: IEEE-754 Floating Point Converter. You can see that the number 4.10 in fact is stored as 4.099999904632568359375. When you multiply it by 100, you get around 409.99999. Casting to (int) causes dropping the fractional part (not rounding), so we get the number 409.

How to operate with currencies in PHP? The best way is to use decimal types or dedicated currency classesMoney is a classic example of a value object as a Domain-Driven Design building block.

If I had a dime for every time I’ve seen someone use FLOAT to store currency, I’d have $999.997634 – Bill Karwin