Successful commercial software applications have to deal with internationalization and localization so that the software can be distributed to other countries in the world, no matter if it is a desktop application or an online service which is used via the Internet. Besides the translation of the language, it is also important to incorporate regional differences such as number and date formatting, or the display of the regional currency.
This article provides an introduction to Java Locale and Currency, the Java based Currency retrieval and provides further detail on how to embed more advanced currency conversions in your application logic.
Java Locale
Regional characteristics are coded into the Local object of java.util. According to the API documentation, a Locale object
… represents a specific geographical, political, or cultural region.
Locales are commonly used to tailor information to the end user, such as date and number format representations. To achieve localization, a Locale object consists of a language, a script (e.g. Latin or Cyrillic), a country and some additional fields like variant or extension that contain additional formatting information.
Java Currency
The Currency object of Java is a representation of the ISO 4216 currency code list. To retrieve a Currency object, you are supposed to call one of the getInstance methods. One of those methods returns a Currency based on a Locale object.
1 2 3 4 5 |
public final class Currency implements Serializable { ... public static Currency getInstance(Locale locale) { ... } ... } |
Based on the method signature, it seems that any Local object can be mapped against a Currency. However, it turns out that only Locale objects that fulfill a set of preconditions can be actually used to instantiate a Currency object.
Discovering the Relation between Locales and Currencies
First of all, a Currency and a Locale do not maintain a common reference that can be used to navigate between both types. A Currency only maintains attributes that describe a currency in more detail, such as a currencyCode or a symbol. On the other side, Locale objects hold only information that are related to language or country settings. So there is no direct relation between both classes, other than both have the capability to be serialized.
However, since a Currency has an instantiation method, Java provides some application logic to derive a Currency from a Locale object.
Converting Locales into Currencies in Java
Locale objects can be initialized with a subset of all attributes. It is therefore possible to instantiate an Locale by passing in e.g. only the language information to the constructor. However, when we want to instantiate a Currency, the country information becomes important, since Java can not interpret language-only Locale parameters and throws an IllegalArgumentException.
1 2 3 4 5 6 |
Locale locale = new Locale('en') Currency currency = Currency.getInstance(locale) java.lang.IllegalArgumentException at java.util.Currency.getInstance(Currency.java:362) at java_util_Currency$getInstance.call(Unknown Source) |
Although the exception handling may be misleading, since we passed in a valid Locale object. The application behavior makes sense, since currencies relate rather to a country, not a language. English is a language spoken in many countries such as Great Britain, the United States, Canada, New Zealand or Australia. However each of these countries maintains their own currency. So even if the Locale object is totally valid, it does not contain enough information to derive the desired information.
Advanced Conversions via Spring Converters
To avoid scenarios where your application does not provide detailed failure information, you might want to wrap the Java conversion logic in your own converter and provide a service for that. Since Spring 3.x, type conversion facilities have become part of spring-core. One option to wrap your conversion logic would be therefore to implement a Locale to Currency converter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
final class LocaleToCurrencyConverter implements Converter<Locale, Currency> { ... public Currency convert(Locale locale) throws IllegalArgumentException { LOGGER.debug("Converting locale [{}] to currency", locale); if (null != locale.getCountry() && !locale.getCountry().isEmpty()) { Currency currency = Currency.getInstance(locale); LOGGER.trace("Successfully converted locale [{}] to currency [{}]", locale, currency); return currency; } LOGGER.warn("Unable to assign a valid currency to locale [{}]", locale); throw new IllegalArgumentException("Locale for language [" + locale.getLanguage() + "] does not contain a country information"); } ... } |
To expose the converter, you may want to implement a conversion services and add the converter to the service.
1 2 3 4 5 6 7 8 9 |
@Service public class CurrencyConversionService extends GenericConversionService { ... public CurrencyConversionService() { super(); this.addConverter(new LocaleToCurrencyConverter()); } ... } |
Once the service is in place, you can simply add the conversion service to your application context and retrieve the Currency from your newly created service.
1 |
<span style="font-weight: 400;">Currency currency = localeToCurrencyConverter.convert(Locale.US);</span> |
The Good, the Bad and the Ugly
Overall, I would like to conclude that Java’s functionality to derive currency information from regional information is quite powerful and easy to use. However the approach has two limitations, from my perspective
First of all, there is definitely room to improve the built in exception handling. It would be desirable, if a failed conversion provides detailed information on the cause instead of the generic IllegalArgumentException.
The second limitation is related to the support of multiple currencies per country. Currently, Java supports only a single currency per country, which covers the reality in most of the countries. However, some countries, such as Serbia-Montenegro, maintain multiple legal tenders at the same time or have a secondary currency next to the country’s official exchange. Those more exotic cases are not built into the core framework.
Finally, if you face one of the exotic use cases and you need to extend the base functionality, you might want to utilize the conversion facilities of your application framework. In this article, I utilized the conversion mechanism of Spring, but other frameworks provide equal support.