Carrying on with my posts about some of the i18n facilities in Java, this time I’m looking at the Currency class. This is a Java util to specifically to represent ISO 4217 currency information.

My main reason for doing these posts is to keep a record for my own sake of the things to watch out for when doing i18n work. It’s also a good chance to look at various interesting cases of the world, and people, being complicated.

As with a lot of Java’s i18n stuff, there was a lot of work done between Java 8 and Java 9, with Java 10 only making a few changes over Java 9.

The code used as the source of this article is here but basically it’s:

get a unicode capable output stream
get a sorted list of all the available Currency types
print a bunch of info about each Currency
 * its ISO 4217 string code
 * the currency symbol
 * the number of fractional digits
 * the DisplayName (in English)
print the count of known Currency types

The raw results are here:

I compiled that with OpenJDK’s Java 8 version, then ran it in OpenJDK’s JVMs for Java 8, 9 and 10. I took the outputs from each and diffed them to see what has changed between Java versions.

Usual Caveat: These results are from the OpenJDK JVM, on a Debian PC. Different JVMs for different OSs on different hardware may have different information. Do your own tests if you need to know for sure!

1. No new currencies

Count: 225

That’s the same in Java 8, 9 and 10. This surprises me becuase there is quite a long period between Java 8 and Java 10. The version of Java 8 I’m using is recently patched, but usually Java doesn’t introduce new i18n data in these patches.

I’m expecting a future version of Java to start including crypto-currencies, but for the moment (despite supporting other odd currencies) it looks like the boom in new currencies hasn’t reached the Java dev/spec teams yet.

2. New currency symbols in Java 9

If you read any of my other i18n posts this won’t be a surprise, but Java 8 used the ASCII 3-char codes for the vast majority of currency symbols; in fact only two got symbols (and neither was a dollar sign!)

code: EUR, symbol: €, fdigits: 2, name: Euro
code: GBP, symbol: £, fdigits: 2, name: British Pound Sterling

… and in Java 9, they made a better job of sprinkling unicode around:

code: AUD, symbol: A$, fdigits: 2, name: Australian Dollar
code: BRL, symbol: R$, fdigits: 2, name: Brazilian Real
code: CAD, symbol: CA$, fdigits: 2, name: Canadian Dollar
code: CNY, symbol: CN¥, fdigits: 2, name: Chinese Yuan
code: HKD, symbol: HK$, fdigits: 2, name: Hong Kong Dollar
code: ILS, symbol: ₪, fdigits: 2, name: Israeli New Shekel
code: INR, symbol: ₹, fdigits: 2, name: Indian Rupee
code: JPY, symbol: JP¥, fdigits: 0, name: Japanese Yen
code: KRW, symbol: ₩, fdigits: 0, name: South Korean Won
code: MXN, symbol: MX$, fdigits: 2, name: Mexican Peso
code: NZD, symbol: NZ$, fdigits: 2, name: New Zealand Dollar
code: TWD, symbol: NT$, fdigits: 2, name: New Taiwan Dollar
code: USD, symbol: US$, fdigits: 2, name: US Dollar
code: VND, symbol: ₫, fdigits: 0, name: Vietnamese Dong
code: XCD, symbol: EC$, fdigits: 2, name: East Caribbean Dollar

A surprising thing here is that none of the currencies use the generic currency symbol ¤. Although it shouldn’t be used for any specific currency I am surprised they didn’t use it as the symbol for some of the placeholder currencies (mentioned later on).

3. Pseudo-currencies

The ISO (and Java) currency list also include a bunch of “supranational” currencies to make various banking and financial tasks easier. These seem to fall into two main groups.

Firstly, there are things which aren’t really currencies, but do get used to transfer currency-like values. These all have a currency code starting with an ‘X’, things like:

code: XAG, symbol: XAG, fdigits: -1, name: Silver
code: XAU, symbol: XAU, fdigits: -1, name: Gold
code: XBA, symbol: XBA, fdigits: -1, name: European Composite Unit

Some of these pseudo-currencies do look like they may be real ones, but the rule to follow appears to be: if its code starts with an ‘X’, it’s a weird one.

code: XCD, symbol: EC$, fdigits: 2, name: East Caribbean Dollar

4. Placeholder/test currencies

The other group of “supranational” currencies is things which really aren’t currencies at all, but placeholders for where a real currency will be used. Again they all start with an ‘X’ in their code, and they all have “-1” fraction digits (I’ll mention that below).

code: XDR, symbol: XDR, fdigits: -1, name: Special Drawing Rights
code: XTS, symbol: XTS, fdigits: -1, name: Testing Currency Code
code: XUA, symbol: XUA, fdigits: -1, name: ADB Unit of Account
code: XXX, symbol: XXX, fdigits: -1, name: Unknown Currency

The ISO docs and Wikipedia explain more, but if you see those XTS or XXX currencies check very carefully what you’re dealing with. It’s unlikely to be real and it’s very likely to be awkward data.

5. Fractional Digits

It’s quite often forgetten that not every currency has “pence and cents”. It’s also true that they don’t all use 2 fraction digits. I’ve dealt with Japanese Yen frequently, which has no fraction digits, but I’ve never dealt with a currency that had more than 2 fraction digits. However, they do exist, some go up to 4, which could cause interesting edge cases.

Just to throw some craziness into this mix, those pseudo-currencies I mentioned above - Java gives them “-1” fraction digits. Yup, you read that right:

code: XAG, symbol: XAG, fdigits: -1, name: Silver
code: XAU, symbol: XAU, fdigits: -1, name: Gold

Now this is exceptionally weird - because the ISO standard doesn’t state “-1” it instead says that these currencies don’t have a fractional part. Specifically, they state “N.A.” or “.” rather than the “0” that some real currencies use.

In the interests of pragmatism, you probably want to explicitly catch these currencies and force those numbers to have zero decimals. Or alternatively, have a special handler case if the currency code starts with an ‘X’.

So take care formatting numbers for currencies and maybe even revisit your rounding rules for calculations. You may need to handle currencies that need extra accuracy after the decimal and to handle cases where Java throws you a nasty negative.

6. Time travelling in the USA

Most currencies have one entry, or maybe 2 if they had some interesting politics recently. But the US Dollar, it has 3 entries, ‘cos yeah America!

code: USD, symbol: USD, fdigits: 2, name: US Dollar
code: USN, symbol: USN, fdigits: 2, name: US Dollar (Next day)
code: USS, symbol: USS, fdigits: 2, name: US Dollar (Same day)

Wikipedia has a bit of info about why - it looks like USS may be falling out of Java’s currency list in a subsequent version.

7. Formatting things the easy way

I18n compatible formatting is never fun. These results show a bunch of things to be aware of when displaying currencies. Things like:

  • Longest (in English) display name is BAM - “Bosnia-Herzegovina Convertible Mark”
  • Shortest is a bunch of them which just use the 3 char code.
  • The currency symbol might be a 3 (or 4!) char code, a single unicode symbol, or a symbol and 1 or 2 chars
  • I mentioned fraction digits above. I’m mentioning them again here - be careful with them!
  • The names and symbols may have special chars in them but the 3-char codes are always ASCII
  • and a final one that caused me hassle in the past: Some countries & currencies put their symbol/code after the number, some before.

Luckily, almost all of the formatting woes you may be about to dive into are now taken care of by Java itself. The main thing to remember is:

Where possible, use the NumberFormat, Currency and Locale facilities to construct the situation you’re working with; maybe something like (JShell)…

var loc = Locale.JAPAN;
var nf = NumberFormat.getCurrencyInstance(loc);
var amount = 123456.789;
==> "£123,457"

That’s printing a monetary amount of 123456.789, in GBP currency, in the format used in Japan. So we get the GBP symbol, but the amount is rounded to fit Japanese currency formatting.

8. Rolling your own

There could be good reasons to define your own currency. Maybe you’re adding support for crypto-currencies and want to keep your existing currency code as clean as possible. Maybe you need to support some historical currencies.

Whatever the reason, the Java Currency class describes how to define new currencies using system properties. That includes specifying a start date for the currency, but no end date. So if you’re doing historical stuff, you may also need to add some sort of extra “stopper” currency to act as an end date.

9. Java 8 to 9, the big i18n push

As I’ve mentioned before, the Java team did a huge amount of i18n work for Java 9. To summarise the changes in Java 9 I spotted from my diff trawling…

  • Unicode All The Things!!! (ahem, except the ISO codes)
  • More actual currency symbols and names, rather than slapping in the code again
  • Using em-dash for date ranges
  • A bunch of work adjusting display names
  • and finally, GBP lost its “Stirling” in Java, although ISO still says “Pound Stirling”

10. Java 9 to 10, nah we’re good

There are literally zero diffs between the Java 9 and 10 results.

Which is nice.