As professional software developers, most of us will be faced sooner or later with the chore of dealing with monetary values. In fact it is very likely that you have already encountered the class java.util.Currency that was introduced to the JDK in Java 1.4. This class represents currencies based on ISO-4217. Additionally java.text.DecimalFormat supports formatting of numbers as monetary values. Throw in the basic arithmetic operators and it would appear on the surface that all of the raw tools for currency representation, computation and display are already present. But if you look more closely, many requirements can only be insufficiently covered.
Surprisingly the class java.util.Currency is missing several important ISO currencies. For example the codes CHE (Swiss Franc Euro) and CHW (Swiss Francs WIR) are conspicuously absent. (In contrast, the various codes for US Dollar; USD, USS, USN; are all available.)
Since the release of Java 7, it is also possible to add custom currencies, but this requires the developer to write and register a so-called “Java extension” (a Java archive, which is installed into the JRE lib folder), which for many companies is not an option. Also the current Currency class cannot model dynamic behaviour as needed in a multitenancy or in a Java EE environment. Additional use cases such as virtual currencies are scarcely covered at all.
Although the class java.text.DecimalFormat offers many basic formatting functions, more robust formatting capabilities are lacking. When passing a formatting pattern to its constructor it is only possible to pass
- a pattern for positive values, including zero, e.g. ##0.00
- or a positive pattern and an additional pattern for negative values, separated by a semicolon, e.g. ##0.00;(##0.00)
However, very often more dynamic behaviour is required; for example varying the format based on the size of the monetary amount, or employing a special pattern to represent zero. On top of that, not all international currencies use the standard 3 digit comma separated groupings, and using the current tools we cannot define flexible groupings such as those necessary for example in Indian rupees where all but the last are groups of two separated by commas, and the last is a group of three, (e.g. INR 12,23,123.34).
Additionally, the lack of thread safety of the format classes is a well known issue that must be handled by developers as a separate case.
Finally an abstraction for monetary arithmetic is completely missing. You might be tempted to use BigDecimal, but you then must be sure to transport the currency with its amount, which is a complexity we will address below. (And don’t even think about using doubles for any serious financial calculations, or you will quickly be faced with rounding errors, because floating-point arithmetic is not suited for financial applications). As a further consequence of these limitations functions such as currency conversion and monetary rounding are also absent.
JSR 354 addresses the standardization of currencies and monetary amounts in Java. The goal of the JSR was to add a flexible and extensible API to the Java ecosystem, which would make working with monetary amounts simpler and safer. It provides support for the gamut of numerical requirements, defines functional extension points, and also provides functions for currency conversion and formatting.
JSR 354 was launched in the spring of 2012 and a year later, the early draft review was published. Our original intention was to integrate the new API’s into Java 9 as part of the platform. However it quickly became obvious that that goal was aggressive, and so the JSR has been driven as a separate specification. In the shadow of Java EE 7 and Java 8, JSR 354 has continuously developed, and we expect it to be finalized in the coming weeks.
Modeling of currencies
As mentioned, the existing Currency class is based on ISO-4217. In our JSR meetings we have discussed intensively the topic of additional characteristic required to support the emerging wave of social, historical and virtual currencies.
But in addition to those advanced requirements ISO 4217 has some more basic and important limitations. First, the ISO typically lags behind the standardization of new currencies Additionally there are ambiguities; for example the code CFA, which is the same for 14 countries from the former CFA-Franc zone, might refer to two different currencies; the central African currency CFA-Franc BEAC, and the west-African currency CFA-Franc BCEAO. Another example would be aspects that are not covered at all in the ISO standard, such as different rounding modes, legal tender, and historical currencies. Even worse, deprecated codes might be reassigned after some time. Nevertheless, it has been shown that the essence of a currency can be modelled using just four methods which comprise our new CurrencyUnit, (and for the most part these are also present in the existing Currency class):
public interface CurrencyUnit {
String getCurrencyCode();
int getNumericCode();
int getDefaultFractionDigits();
CurrencyContext getCurrencyContext(); // new
}
Using the new APIs Currency codes for non-ISO-currencies are freely selectable. Thus you can define you own code systems or integrate your existing code. The naming for your own proprietary code is very flexible. If it is not already an ISO currency, you are pretty much free to use it as long as it is unique. All of the following codes are therefore possible:
CHF // ISO
CS:23345 // Proprietary currency code
45-1 // Proprietary currency code
CurrencyUnit
instances themselves can be accessed from the MonetaryCurrencies
, a singleton class analogous to java.util.Currency
.
Contrast the existing Currency
class in which only one currency is ever returned for a given locale. The JSR 354 API generally supports substantially more complex scenarios in which a CurrencyQuery
can be supplied with arbitrary attributes, using a fluent syntax reminiscent of the new Java Date and Time APIs. Thus, for example, the query “all European currencies that were in effect in 1970” could be accessed as follows:
Set<CurrencyUnit> currencies = MonetaryCurrencies
.getCurrencies (CurrencyQueryBuilder.of()
.set ("continent", "Europe")
.set (Year.of(1970)).build());
In the background there is an associated Service Provider Interface (SPI) that implements the query functionality provided by the API, where multiple providers can service appropriate currencies. You can provide your own custom provider where required. (More on SPI’s below.) The query options available depend solely on the registered providers. For more details, please refer to the JSR 354 documentation and reference implementation.
Modeling of monetary amounts
Intuitively, one would probably just put together a currency unit with a numeric value as say a BigDecimal, and assemble it into a new immutable value type. Unfortunately it has been shown that this model fails because of the enormous range of requirements for monetary amount types. For example in trading scenarios we would need an extremely fast computation, low memory-consumption type, whereas some compromises in the numerical capabilities may be acceptable (for example decimal precision). In contrast, we often require very high precision for product invoicing calculations, and for that we might sacrifice some execution time. Finally, risk calculations and statistics can produce very large numbers, that may exceed the numeric range and capabilities of the numeric type in use.
In summary, it would hardly be possible to cover all of these requirements within a single implementation type, so we decided to support multiple implementations simultaneously. An amount is modelled by the MonetaryAmount
interface. For interoperability, rules have been defined that prevent unexpected rounding errors. Also a so-called MonetaryContext
was added to provide additional meta-data about the underlying implementation type, such as its numerical capabilities (precision and scale) and other properties:
public interface MonetaryAmount
extends CurrencySupplier, NumberSupplier, Comparable<MonetaryAmount> {
CurrencyUnit getCurrency();
NumberValue getNumber();
MonetaryContext getMonetaryContext();
<R> R query (MonetaryQuery<R> query);
MonetaryAmount with(MonetaryOperator operator);
MonetaryAmountFactory <? extends MonetaryAmount> getFactory ();
// ...
}
The numerical value of an amount is returned as javax.money.NumberValue
, which extends java.lang.Number
adding functions to export the numerical value correctly and loss-free. The two methods with
and query
define extension points used by functionality such as rounding, currency conversion or predicates. MonetaryAmount
also provides operations to compare amounts with each other or apply arithmetic operations analogous to BigDecimal
. Finally, each amount provides a MonetaryAmountFactory
, which can be used to create any kind of amounts with the same type implementation.
Creating Monetary Amounts
Amounts are generated using a MonetaryAmountFactory
. These factories can be obtained from the MonetaryAmounts
singleton. In the simplest case, you would use the (configurable) default factory to create a new MonetaryAmount
instance:
MonetaryAmount amt = MonetaryAmounts.getDefaultAmountFactory()
.setCurrency("EUR")
.setNumber(200.5)
.create();
Also, you can obtain a MonetaryAmountFactory
explicitly by passing the desired implementation type as a parameter to MonetaryAmounts
. Similar to currencies, amount factories can be queried using a MonetaryAmountFactoryQuery
:
MonetaryAmountFactory<?> factory = MonetaryAmounts
.getAmountFactory(
MonetaryAmountFactoryQueryBuilder.of ()
.setPrecision (200)
.setMaxScale(10)
.build ());
Rounding of Monetary Amounts
As we have seen before, to each MonetaryAmount
an instance of MonetaryOperator
can be passed (by calling the with
method), to run any external functionality that returns another amount as a result:
@FunctionalInterface
public interface MonetaryOperator
extends UnaryOperator <MonetaryAmount> {}
This mechanism is also used for rounding of monetary amounts. Nevertheless a rounding is defined by the interface MonetaryRounding
, which additionally provides a RoundingContext
. In general the following types of rounding were considered:
- Internal rounding done implicitly due to the implementation of the numerical model used. For example suppose an implementation is declared to support a scaling of up to five decimal places. Now imagine we calculate
CHF 10 / 7
. This ends up in a repeating decimal. In this case the resulting amount is allowed to be rounded implicitly to the maximal five decimal scale supported by the user defined implementation, resulting inCHF 1.42857
. - External rounding may be needed when the numeric value of an amount is exported to a numeric representation with lower numerical capabilities. For example if for some reason we need to represent an amount in a single byte (which we are obviously not recommending but for the sake of the example), then an amount 255.15 can be rounded to 255.
- Format Rounding may “round” an amount in some arbitrary way. For example,
CHF 2'030'043
can be displayed asCHF> 1 million
.
Basically internal (and thus implicit) rounding is allowed only in cases similar to the above. All other rounding types are explicitly applied by the developer. This makes sense since where and how rounding has to be applied depends largely on the use case. Thus, you need maximum control over what happens.
Several rounding types are accessible out of the box from the MonetaryRoundings
singleton: you can access rounding operators for currencies as well as mathematical rounding matching a given MathContext
. Like other areas you can also pass a RoundingQuery
for complex cases. The JSR itself does not define any further APIs, so users should add their own implementations of roundings. As a hypothetical example let’s provide a rounding for cash payments in Swiss Francs (CHF). In Switzerland the smallest unit available for cash payments is 5 Rappen (5 cents). Therefore the corresponding rounding must round on these 5 sub-units. Consequently we need a mechanism to access the newly defined rounding. By default, when accessing a rounding for CHF without any further attributes passed, we get a default rounding based on the default fractional units of the currency, which for CHF is equal to 1/100. So we need to pass an additional flag that tells the providers we want to query the cash rounding instead of the default rounding for CHF. For that we might model an additional enum type, that defines the type of rounding we want to access:
public enum RoundingType{
CASH, DEFAULT
}
Given we have registered accordingly a "RoundingProvider" we can now access the cash rounding as follows:
MonetaryRounding cashRounding =
MonetaryRoundings.getRounding(
RoundingQueryBuilder.of ()
.setCurrency(MonetaryCurrencies.getCurrency("CHF"))
.set(RoundingType.CASH)
.build ());
The rounding itself can then be easily applied to any amount:
MonetaryAmount amount = Money.of("CHF", 1.1221);
MonetaryAmount roundedCHFCashAmount = amount.with(rounding);
// result: CHF 1.10
Currency Conversion
The heart of the currency conversion is the ExchangeRate
. This includes, together with the involved source and destination currencies, the conversion factor and other metadata. Multi-stage conversions (e.g. triangular rates) are supported as well. Exchange rates are always unidirectional and are supplied by the so-called ExchangeRateProvider
instances. Like with amounts, currencies and roundings, as well as a ConversionQuery
can be passed to define the desired conversion in more detail. The CurrencyConversion
operation itself extends MonetaryOperator
adding a reference to an exchange rate provider and the target currency.
Both interfaces (ExchangeRateProvider
, CurrencyConversion
) can be obtained from the MonetaryConversions
singleton:
CurrencyConversion conversion = MonetaryConversions
.getConversion("USD");
ExchangeRateProvider prov = MonetaryConversions
.getExchangeRateProvider();
Additionally, you can pass in more than one rate provider name to the calls above, thereby defining a chain of providers to be used. Similarly to accessing a default amount factory, you can also configure a default chain. Currency conversion is then performed similarly to rounding:
MonetaryAmount amountCHF = ...;
MonetaryAmount convertedAmountUSD = amount.with(conversion);
The current reference implementation comes with two preconfigured providers, which provide conversion factors based on public data feeds of the European Central Bank and the International Monetary Fund. For certain currencies data provided goes back to 1990.
Formatting
The goal of the formatting API for amounts was to make it both simple and flexible, and to overcome some of the drawbacks of the existing formatting API of Java, most notably the lack of thread safety:
public interface MonetaryAmountFormat
extends MonetaryQuery<String>{
AmountFormatContext getAmountFormatContext();
String format (MonetaryAmount amount);
void print (Appendable appendable, MonetaryAmount amount)
throws IOException;
MonetaryAmount parse(CharSequence text)
throws MonetaryParseException;
}
Instances of this interface can be obtained from the MonetaryFormats singleton:
MonetaryAmountFormat fmt =
MonetaryFormats.getAmountFormat(Locale.US);
Also here you can pass a AmountFormatQuery containing any parameters to configure the formatting acquired:
DecimalFormatSymbols symbols = ...;
MonetaryAmountFormat fmt =
MonetaryFormats.getAmountFormat (
AmountFormatQueryBuilder.of(Locale.US)
.set(symbols)
.build ());
So finally only the capabilities of the registered SPI instances define here what is possible.
SPIs
In addition to the core API, JSR 354 also provides a complete Service Provider interface that allows you to tailor all functions as needed. Thus, adding currencies, conversions, rounding, formats, or amount implementations is easy. Finally by adapting the bootstrapping logic also dynamic/contextual behaviour can be provided, or SPIs can be implemented and managed within CDI.
Conclusion
JSR 354 defines a simple yet powerful API that greatly simplifies the handling of currency and monetary amounts, also covering advanced topics such as rounding and currency conversion. On top of that it adds a simple but flexible formatting API for monetary amounts. The functional extension points provide the ability to easily add additional functionality, forming a useful example of the power of the functional concepts introduced with Java 8. In this regard, it is also worth looking at the Java Money OSS project, where implementations of several financial formulas and a possible integration with CDI are experimentally available.
JSR 354 is planned to be finalized in Q1 of 2015. For users who are still working on Java 7, we plan a forward-compatible backport once the JSR is final.
About the Author
Anatole Tresch is specification lead for JSR 354 (Java Currency & Money) and is also involved in Java EE and configuration. After his studies at the University of Zurich, Anatole worked for several years as a managing partner and consultant. He currently works as a technical coordinator and architect at Credit Suisse.