Specification: Java Money and Currency API for Java 7 Version: 1.0 Status: Final Release Date: April 2015 Copyright: 2012-2015 Credit Suisse AG P.O.Box 8070 Zurich Switzerland All rights reserved. License: Spec Evaluation/Implementation Licence
This document is the user guide of the Java API for Money and Currency of JSR 354 targeting Java 7. The technical objective is to provide a money and currency API for Java, targeted at all users of currencies and monetary amounts, compatible with Android and Java 7. The API will provide support for standard [ISO-4217] and custom currencies, and a model for monetary amounts and rounding. It will have extension points for adding additional features like currency exchange, financial calculations and formulas. For detailed information on the API and its usage, we recommend reading the JSR’s specification, which can be downloaded from the JCP page http://jcp.org/en/jsr/detail?id=35[JSR 354 on jcp.org .
The APIs for both Java 7 and Java 8 are basically the same. Java 8 additionally provides full support for default methods, which adds lots of comfort implementing the SPIs. For Java 7 users the reference implementation provides a set of common base classes, that provide the same functionality as provided by default methods in the Java 8 version of the API:
public class MyImplClass implements CurrencyProviderSpi{
...
}
you simply have to rewrite it as:
public class MyImplClass extends BaseCurrencyProviderSpi{
...
}
…and it will run similarly in Java 7.
Similar to JSR 354 Java 8 API this the Java 7 API defines 4 main packages:
javax.money
-
contains the main artifacts, such as
CurrencyUnit, MonetaryAmount, MonetaryContext, MonetaryOperator, MonetaryQuery, MonetaryRounding
, and the singleton accessorMonetary
. javax.money.conversion
-
contains the conversion artifacts
ExchangeRate, ExchangeRateProvider, CurrencyConversion
and the accordingMonetaryConversions
accessor singleton. javax.money.format
-
contains the formatting artifacts
MonetaryAmountFormat, AmountFormatContext
and the accordingMonetaryFormats
accessor singleton. javax.money.spi
-
contains the SPI interfaces provided by the JSR 354 API and the bootstrap logic, to support different runtime environments and component loading mechanisms.
The JSR’s source code repository under [source] provides several modules:
- jsr354-api-bp
-
contains the API backport compatible with Java g and 7.
- jsr354-ri-bp
-
contains the 'moneta' implementation, based on Java 7.
- jsr354-api
-
contains the JSR 354 API for use with Java 8. We highly recommend using this version, if possible, to benefit from the power of Java 8.
- jsr354-ri
-
contains the 'moneta' reference implementation, based on Java 8.
- jsr354-tck
-
contains the technical compatibility kit (TCK) of the JSR, requiring as well Java 7. The TCK is the same for Java 7 and Java 8.
- javamoney-parent
-
is a root “POM” project for all modules under
org.javamoney
. This includes the RI/TCK projects, but not jsr354-api and javamoney-api-bp (which are standalone). - javamoney-library
-
contains a financial library (JavaMoney) adding comprehensive support for several extended functionality, built on top of the JSR’s API, but not part of the JSR.
- javamoney-examples
-
finally contains the examples and demos, and also is not part of this JSR.
The following sections illustrate the API usage in more detail.
The Java 7 based implementation of JSR 354 has to provide value type classes for monetary amounts, hereby implementing
javax.money.MonetaryAmount
, and registering at least one implementation class with the javax.money.Monetary
singleton by implementing and registering a corresponding javax.money.MonetayAmountFactory
instance.
As an example the reference implementation provides a class org.javamoney.moneta.Money
, which is using
java.math.BigDecimal
internally:
public final class Money
implements MonetaryAmount, Comparable<MonetaryAmount>, Serializable, CurrencySupplier {
...
}
Since a corresponding MonetaryAmountFactory
is registered, a new instance can be created using the typed factory:
MonetaryAmountFactory<Money> fact = Monetary.getAmountFactory(Money.class);
Money m = fact.withCurrency("USD").with(200.50).create();
Also a generic MonetaryAmount
instance can be accessed using a raw factory (hereby it depends on the configured
default amount factory, which effective type instance is returned):
MonetaryAmount amt = Monetary.getDefaultAmountFactory()
.withCurrency("USD").with(200.50).create();
Still we can evaluate the effective amount’s type effectively:
if(Money.class==amt.getClass()){
Money m = (Money)amt;
}
But in general, we do not need to know the exact implementation in most cases, since we can access amount
meta-data as a MonetaryContext
, This meta-data provides information, such as the maximal precision, maximal scale
supported by the type’s implementation as well as other attributes. Refer to [MonetaryContext] for more details.
MonetaryContext ctx = m.getMonetaryContext();
if(ctx.getMaxPrecision()==0){
System.out.println("Unbounded maximal precision.");
}
if(ctx.getMaxScale()>=5){
System.out.println("Sufficient scale for our use case, go for it.");
}
Finally performing arithmetic operations in both above scenarios works similar as it is when using
java.math.BigDecimal
:
MonetaryAmount amt = ...;
amt = amt.multiply(2.0).subtract(1.345);
Also the sample above illustrates how algorithmic operations can be chained together using a fluent API. As
mentioned also external functionality can be chained, e.g. using instances of MonetaryOperator
:
MonetaryAmount amt = ...;
amt = amt.multiply(2.12345).with(Monetary.getDefaultRounding())
.with(MonetaryFunctions.minimal(100)).
.multiply(2.12345).with(Monetary.getDefaultRounding())
.with(MonetaryFunctions.percent(23));
Since the Money
implementation class, which is part of the reference implementation, internally uses
java.math.BigDecimal
the numeric capabilities match exact the capabilities of BigDecimal
.
When accessing MonetaryAmountFactory
instances it is possible to configure the MathContext
effectively used
(by default Money
uses MathContext.DECIMAL64
).:
MonetaryAmountFactory
, using the RI class Money
as example.MonetaryAmountFactory<Money> fact = Monetary.getAmountFactory(
MonetaryAmountFactoryQueryBuilder.of(Money.class)
.set(new MathContext(250, RoundingMode.HALF_DOWN)).build()
);
// Creates an instance of Money with the given MathContext
MonetaryAmount m1 = fact.setCurrency("CHF").setNumber(250.34).create();
Money m2 = fact.setCurrency("CHF").setNumber(250.34).create();
Now, one last thing to discuss is, how users can add their own functionality, e.g. by writing their own
MonetaryOperator
functions. Basically there are two distinct usage scenarios:
-
When the basic arithmetic defined on each
MonetaryAmount
are sufficient, it should be easy to implement such functionality, since its behaving like any other type, e.g.
public final class DuplicateOp implements MonetaryOperator{
public <T extends MonetaryAmount> T apply(T amount){
return (T) amount.multiply(2);
}
}
Below is a rather academical example of a MonetaryOperator
that simply converts any given amount to an amount with
the same numeric value, but with XXX (undefined) as currency:
MonetaryOperator
using the MonetaryAmountFactory
provided.public final class ToInvalid implements MonetaryOperator{
public <T extends MonetaryAmount> T apply(T amount){
return (T)amount.getFactory().setCurrency("XXX").create();
}
}
This class implements a MonetaryAmount
using long as numeric representation, whereas the full amount is interpreted
as minor units, with a denumerator of 100000
.
As an example CHF 2.5
is internally stored as CHF 250000
. Addition and subtraction of values is trivial, whereas
division and multiplication get complex with non integral values. Compared to Money
the possible amounts to be modeled
are limited to an overall precision of 18
and a fixed scale of 5
digits.
Beside that the overall handling of FastMoney
is similar to Money
. So we could rewrite the former example by
just replacing FastMoney
with Money
:
MonetaryAmountFactory<FastMoney> fact = Monetary.getAmountFactory(FastMoney.class);
// Creates an instance of Money with the given MathContext
MonetaryAmount m1 = fact.setCurrency("CHF").setNumber(250.34).create();
FastMoney m2 = fact.setCurrency("CHF").setNumber(250.34).create();
Of course, the MonetaryContext
is different than for Money
:
maxPrecision = 18; // hard limit maxScale = 5; // fixed scale numeric class = Long attributes: RoundingMode.HALF_EVEN
A total of amounts can be calculated in multiple ways, one way is simply to chain the amounts with add(MonetaryAmount)
:
MonetaryAmountFactory factory = Monetary.getDefaultAmountFactory().setCurrency("CHF");
MonetaryAmount[] params = new MonetaryAmount[]{
factory.setNumber(100).create(),
factory.setNumber(10.20).create(),
factory.setNumber(1.15).create(),};
MonetaryAmount total = params[0];
for(int i=1; i<params.length;i++){
total = total.add(params[i]);
}
As an alternate it is also possible to define a MonetaryOperator
, which can be passed to all amounts:
public class Total implements MonetaryOperator{
private MonetaryAmount total;
public <T extends MonetaryAmount<T>> T apply(T amount){
if(total==null){
total = amount;
}
else{
total = total.add(amount);
}
// ensure to return correct type, since different implementations
// can be passed as amount parameter
return amount.getFactory().with(total).create();
}
public MonetaryAmount getTotal(){
return total;
}
public <T extends MonetaryAmount> T getTotal(Class<T> amountType){
return Monetary.getAmountFactory(amountType).with(total).create();
}
}
Important
|
We are well aware of the fact that this implementation still has some severe drawbacks, but we decided for simplicity to not add the following features to this example:
|
Now with the MonetaryOperator
totalizing looks as follows:
Total total = new Total();
for(int i=1; i<params.length;i++){
total.with(params[i]);
}
System.out.println("TOTAL: " + total.getTotal());
A similar approach can also be used for other multi value calculations as used in statistics, e.g. average, median etc.
The present value (abbreviated PV) shows how financial formulas can be implemented based on the JSR 354 API. A PV models the current value of a financial in- or outflow in the future, weighted with a calculatory interest rate. The PV is defined as follows:
C / ((1+r)^n)
Hereby
-
n
is the time of the cash flow (in periods) -
r
is the discount rate (the rate of return that could be earned on an investment in the financial markets with similar risk.); the opportunity cost of capital. -
C
is the net cash flow i.e. cash inflow – cash outflow, at time t . For educational purposes,
The same financial function now can be implemented for example as follows:
public <T extends MonetaryAmount> T presentValue(
T amt, BigDecimal rate, int periods){
BigDecimal divisor = BigDecimal.ONE.add(rate).pow(periods);
// cast should be safe for implementations that adhere to this spec
return (T)amt.divide(divisor);
}
This algorithm can be implemented as MonetaryOperator
:
public final class PresentValue implements MonetaryOperator{
private BigDecimal rate;
private int periods;
private BigDecimal divisor;
public PresentValue(BigDecimal rate, int periods){
Objects.requireNotNull(rate);
this.rate = rate;
this.periods = periods;
this.divisor = BigDecimal.ONE.add(periods).power(periods);
}
public int getPeriods(){ return periods; }
public BigDecimal getRate(){ return rate; }
public <T extends MonetaryAmount> T apply(T amount){
// cast should be safe for implementations that adhere to this spec
return (T)amount.divide(divisor);
}
public String toString(){...}
}
For simplicity we did not add additional feature such as caching of PresentValue instances using a static factory
method, or pre-calculation of divisor matrices. Now given the MonetaryOperator
a present value can be
calculated as follows:
Money m = Monetary.getAmountFactory(Money.class).setCurrency("CHF").setNumber(1000).create();
// present value for an amount of 100, available in two periods,
// with a rate of 5%.
Money pv = m.with(new PresentValue(new BigDecimal("0.05"), 2));
Currency Conversion also is a special case of a MonetaryOperator
since it creates a new amount based on another
amount. Hereby by the conversion the resulting amount will typically have a different currency and a different
numeric amount:
MonetaryAmount inCHF =...;
CurrencyConversion conv = MonetaryConversions.getConversion("EUR");
MonetaryAmount inEUR = inCHF.with(conv);
Also we can define the providers to be used for currency conversion by passing the provider names explicitly:
CurrencyConversion conv = MonetaryConversions.getConversion("EUR", "EZB", "IMF");
To cover also more complex usage scenarios we can also pass a ConversionQuery
with additional parameters for
conversion, e.g.:
MonetaryAmount inCHF =...;
CurrencyConversion conv = MonetaryConversions.getConversion(ConversionQueryBuilder.of()
.setProviders("CS", "EZB", "IMF")
.setTermCurrency("EUR")
.set(MonetaryAmount.class, inCHF, MonetaryAmount.class)
.set(LocalDate.of(2008, 1, 1))
.setRateType(RateType.HISTORIC)
.set(StockExchange.NYSE) // custom type
.set("contractId", "AA-1234.2")
.build());
MonetaryAmount inEUR = inCHF.with(conv);
[] Public Source Code Repository on GitHub: http://github.com/JavaMoney, Branch/Tag matching updated PDR is {version}
MonetaryFunctions
is not part of the JSR, its just for illustration purposes.