Skip to content

Latest commit

 

History

History
874 lines (683 loc) · 39.3 KB

userguide.adoc

File metadata and controls

874 lines (683 loc) · 39.3 KB

JavaMoney 'Moneta' User Guide

'Moneta' is an implementation of the JSR 354 'Java Money API'. The API is separated so also other can provide their own implementations. This document will mainly focus on the overall library usage from a user’s perspective, when using 'Moneta'. Normally this document will not explicitly differentiate between the JSR 354 API and this implementation, unless it is useful for the common understanding.

This document

This is a user guide that describes all relevant aspects of Java Money, for using this API along with the 'Moneta' reference implementation.

For a shorter introduction you may check out the quick start guide (tbd).

1. Introduction to Java Money

Java Money is a initiative lead by Credit Suisse to standardize monetary aspects in Java. The main part hereby is JSR 354, which defines the money and currency API covering currencies, monetary amounts, rounding, currency conversion and formatting. Moneta is the JSR 354 reference implementation, also adding some additional aspects like extended Lambda-Support and multiple amount implementation classes. Additionally there is the JavaMoney OSS library, which contains additionally financial calculations and formulas, additional currency mapping, regions, historic currencies, currency/region mapping and last but not least EE/CDI support. Below given the most important links:

Basically the API of JSR 354 provides the following packages:

javax.money

contains the main artifacts, such as CurrencyUnit, MonetaryAmount, MonetaryContext, MonetaryOperator, MonetaryQuery, MonetaryRounding, and the singleton accessors MonetaryCurrencies, MonetaryAmounts, MonetaryRoundings..

javax.money.convert

contains the conversion artifacts ExchangeRate, ExchangeRateProvider, CurrencyConversion and the according MonetaryConversions accessor singleton..

javax.money.format

contains the formatting artifacts MonetaryAmountFormat, AmountFormatContext and the according MonetaryFormats 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.

Basically the JSR 354 API is complete, meaning users won’t have to reference anything other than what is already part of the JSR’s API. As a consequence this reference implementation contains mostly components that are registered into the API using the JSR’s SPI mechanism. Only a few additions to the API are done, e.g. singletons providing Lambda-supporting methods (MonetaryFunctions).

2. Working with Currency Units

2.1. Accessing Currency Units

Basically access to currency units is based on the javax.money.MonetaryCurrencies singleton. Hereby you can access currencies in different ways:

2.1.1. Access currencies by currency code

You can use the currency code to access currencies.

Accessing currencies by currency code
CurrencyUnit currencyCHF = Monetary.getCurrency("CHF");
CurrencyUnit currencyUSD = Monetary.getCurrency("USD");
CurrencyUnit currencyEUR = Monetary.getCurrency("EUR");

Hereby all codes available from java.util.Currency in the underlying JDK are mapped by default.

2.1.2. Access currencies by Locale

You can use java.util.Locale to access currencies. Hereby the Locale instance, represents a country. All available countries can be accessed by calling Locale.getISOCountries(). With the given ISO country code a corresponding Locale can be created:

String isoCountry = "USA";
Locale country = new Locale("", isoCountry);

Similarly to java.util.Currency a CurrencyUnit can be accessed using this Locale:

Accessing currencies by Locale
CurrencyUnit currencyCHF = Monetary.getCurrency(new Locale("", "SUI")); // Switzerland
CurrencyUnit currencyUSD = Monetary.getCurrency(new Locale("", "USA")); // United States of America
CurrencyUnit currencyEUR = Monetary.getCurrency(new Locale("", "GER")); // Germany

Hereby all codes available in the underlying JDK are mapped by default.

2.1.3. Accessing all currencies

Also all currently known currencies can be accessed:

Accessing all currencies
Collection<CurrencyUnit> allCurrencies = Monetary.getCurrencies();

Similarly to other access methods you can also explicitly specify the provider chain to be used. The Moneta reference implementation provides the following currency providers:

  • default: this currency provider (implemented by org.javamoney.moneta.spi.JDKCurrencyProvider) simply maps/adapts java.util.Currency.

  • ConfigurableCurrencyUnitProvider (implemented by org.javamoney.moneta.spi.ConfigurableCurrencyUnitProvider) provides a configuration hook for programmatically add instances. This provider is autoconfigured. Ir provides static hooks for adding additional CurrencyUnit instances:

Example of registering CurrencyUnit instances programmatically.
 /**
 * Registers a bew currency unit under its currency code.
 * @param currencyUnit the new currency to be registered, not null.
 * @return any unit instance registered previously by this instance, or null.
 */
public static CurrencyUnit registerCurrencyUnit(CurrencyUnit currencyUnit);

/**
 * Registers a bew currency unit under the given Locale.
 * @param currencyUnit the new currency to be registered, not null.
 * @param locale the Locale, not null.
 * @return any unit instance registered previously by this instance, or null.
 */
public static CurrencyUnit registerCurrencyUnit(CurrencyUnit currencyUnit, Locale locale);

/**
 * Removes a CurrencyUnit.
 * @param currencyCode the currency code, not null.
 * @return any unit instance removed, or null.
 */
public static CurrencyUnit removeCurrencyUnit(String currencyCode);

/**
 * Removes a CurrencyUnit.
 * @param locale the Locale, not null.
 * @return  any unit instance removed, or null.
 */
public static CurrencyUnit removeCurrencyUnit(Locale locale);

The API is straightforward so far. For most cases the BuildableCurrencyUnit class can be used to create additional currency instances that then can be registered using the static methods:

2.1.4. Registering Additional Currency Units

For adding additional CurrencyUnit instances to the MonetaryCurrencies singleton, you must implement an instance of CurrencyProviderSpi. Following a minimal example, hereby also using the BuildableCurrencyUnit class, that also provides currencies for Bitcoin:

Implementing a Bitcoin currency provider
public final class BitCoinProvider implements CurrencyProviderSpi {

    private Set<CurrencyUnit> bitcoinSet = new HashSet<>();

    public BitCoinProvider() {
       bitcoinSet.add(CurrencyUnitBuilder.of("BTC", "MyCurrencyBuilder").build());
       bitcoinSet = Collections.unmodifiableSet(bitcoinSet);
    }

    /**
     * Return a {@link CurrencyUnit} instances matching the given
     * {@link javax.money.CurrencyQuery}.
     *
     * @param query the {@link javax.money.CurrencyQuery} containing the parameters determining the query. not null.
     * @return the corresponding {@link CurrencyUnit}s matching, never null.
     */
    @Override
    public Set<CurrencyUnit> getCurrencies(CurrencyQuery query) {
       // only ensure BTC is the code, or it is a default query.
       if (query.isEmpty()
           || query.getCurrencyCodes().contains("BTC")
           || query.getCurrencyCodes().isEmpty()) {
           return bitcoinSet;
       }
       return Collections.emptySet();
    }

}

By default, the BitCoinProvider class must be configured as service to be loadable by java.util.ServiceLoader. This can be achieved by adding a file META-INF/services/javax.money.spi.CurrencyProviderSpi with the following content to your classpath:

Contents of META-INF/services/javax.money.spi.CurrencyProviderSpi
# assuming the class BitCoinProvider is in the package my.fully.qualified
my.fully.qualified.BitCoinProvider

Alternatively, if the JSR’s Bootstrap logic uses CDI, it would also be possible to register the provider class as normal CDI bean, e.g.

Implementing a Bitcoin currency provider
@Singleton
public class BitCoinProvider implements CurrencyProviderSpi {
  ...
}

Now given this example it is obvious that the tricky part is to define, when exactly a given CurrencyQuery should be targeted by this provider, or otherwise, be simply ignored. Our case just provides an additional currency code, so it is a good idea to just only return data for default query types. Additionally we only return our code sublist, when the according code is requested, or a unspecified request is performed.

2.1.5. Building Custom Currency Units

You can use the MonetaryCurrencies static methods to register currencies as follows.

Example of registering CurrencyUnit instances programmatically.
CurrencyUnit unit = CurrencyUnitBuilder.of("FLS22", "MyCurrencyProvider")
    .setDefaultFractionDigits(3)
    .build();

// registering it
Monetary.registerCurrency(unit);
Monetary.registerCurrency(unit, Locale.MyCOUNTRY);

Fortunately CurrencyUnitBuilder is also capable of registering a currency on creation, by just passing a register flag to the call: So the same can be rewritten as follows:

Example of registering CurrencyUnit instances programmatically, using CurrencyUnitBuilder.
CurrencyUnitBuilder.of("FLS22", "MyCurrencyProvider")
    .setDefaultFractionDigits(3)
    .build(true /* register */);

2.1.6. Provided Currencies

Moneta, by default provides only the same currencies as defined by java.util.Currency. Use the extended currency module from the JavaMoney OSS library for additional currency support, e.g. current overloading of currencies based on the actual input from the online ISO-4217 resources.

2.2. Monetary Amounts

Monetary amounts are the key abstraction of JSR 354. Moneta hereby provides different implementations of amounts:

  • Money represents a effective implementation, which is based on java.math.BigDecimal internally for performing the arithmetic operations. The implementation is capable of supporting arbitrary precision and scale.

  • FastMoney represents numeric representation that was optimized for speed. It represents a monetary amount only as a integral number of type long, hereby using a number scale of 100'000 (10^5).

  • RoundedMoney finally provides an amount implementation that is implicitly rounded after each operation.

2.2.1. Choosing an Implementation

Basically, if the numeric capabilities of FastMoney are sufficient for your use cases, you may use this type. If not sure, using Money is in general safe. RoundedMoney should only be used, if you are well aware of its usage, since the immediate rounding may produce unwanted side effects (invalid values).

2.2.2. Creating new Amounts

As defined by the JSR’s API you can access according MonetaryAmountFactory for all types listed above to create new instances of amounts. E.g. instances of FastMoney can be created as follows:

Creating instances of FastMoney using the Monetary singleton:
FastMoney m = Monetary.getAmountFactory(FastMoney.class).setCurrency("USD").setNumber(200.20).create();

Additionally Moneta also supports static factory methods on the types directly. So the following code is equivalent:

Creating instances of FastMoney using the static factory method:
FastMoney m = FastMoney.of(200.20, "USD");

Creation of Money instances is similar:

Creating instances of Money:
Money m1 = Monetary.getAmountFactory(Money.class).setCurrency("USD").setNumber(200.20).create();
Money m2 = Money.of(200.20, "USD");
Configuring Instances of Money

The Money class is internally based on java.math.BigDecimal. Therefore the arithmetic precision and rounding capabilities of BigDecimal are also usable with Money. Hereby, by default, instances of Money internally are initialized with MathContext.DECIMAL64. Nevertheless instance also can be configured explicitly by passing a MathContext as part of a MonetaryContext:

Creating instances of Money configuring the MathContext to be used.
Money money = Money.of(200, "CHF", MonetaryContextBuilder.of().set(MathContext.DECIMAL128).build());

Using the JSR’s main API allows to achieve the same as follows:

Creating instances of Money configuring the MathContext to be used, using the MonetaryAmountFactory.
Money money = Monetary.getAmountFactory(Money.class)
                              .setCurrencyUnit("CHF").setNumber(200)
                              .setContext(MonetaryContextBuilder.of().set(MathContext.DECIMAL128).build())
                              .create();

Additionally the default MathContext can be configured with the javamoney.properties located in your classpath:

Configuring the default MathContext to be used for Money.
org.javamoney.moneta.Money.defaults.mathContext=DECIMAL128

Alternatively you also can configure the precision and RoundingMode to be used:

Configuring the default MathContext to be used for Money (alternative).
org.javamoney.moneta.Money.defaults.precision=DECIMAL128
org.javamoney.moneta.Money.defaults.roundingMode=HALF_EVEN

2.2.3. Configuring Internal Rounding of FastMoney

The class FastMoney internally uses a single long value to model a monetary amount. Hereby it uses a fixed scale of 5 digits. Obviously this may require rounding in some cases. Hereby by default FastMoney rounds input values (of type MonetaryAmount, or numbers) to its internal 5 digits scale. In most cases that makes sense and makes use of this class easy and straight forward. Nevertheless there might be scenarios, where you want to throw ArithmeticException if an entry value exceeds the maximal scale. This alternate, more rigid behaviour, can be activated by adding the following configuration to javamoney.properties:

Activating strict input number validation for FastMoney
org.javamoney.moneta.FastMoney.enforceScaleCompatibility=true

2.2.4. Registering Additional Amount Implementations

By default, additional implementation classes are added, by registering an instance of MonetaryAmountFactoryProviderSpi as JDK services loaded by java.util.ServiceLoader. For this you have to add the following contents to META-INF/services/javax.money.spi.MonetaryAmountFactoryProviderSpi:

Providing custom monetary amount implementations
my.fully.qualified.MonetaryAmountFactoryProviderImplClass

For further ease of use, your implementations may furthermore provide static factory methods, e.g.

Static factory methods of the custom monetary amount implementation:
public static MyMoney of(String currencyCode, double number);
public static MyMoney of(String currencyCode, long number);
public static MyMoney of(String currencyCode, Number number);

Hereby several commonly used functionality can be reused from the moneta RI, e.g. safe conversion of any JDK number type to BigDecimal is available on MoneyUtils, along with additional helpful methods.

2.2.5. Mixing Amount Implementation Types

Basically the JSR supports mixing of different implementation types. Nevertheless there are some effects that are important to mention, if doing so:

  • the performance may decrease based on the slower implementation used. Hereby the type used as a base type (the type on which the operations are performed), is the type that basically determines overall performance.

  • mixing of different amount implementation types may require internal rounding to be performed. Whereas the compatibility of precision is ensured, scale may be reduced silently as needed.

Nevertheless there are strategies to mitigate these possible issues. The most easy and obvious strategy hereby is simply converting explicitly to the required target type, before performing any operations. This can be easily achieved, since every implementation in moneta provides corresponding static from() methods:

Using the custom monetary amount implementation with Money:
MyMoney money1;
Money money = Money.from(myMoney);
FastMoney fastMoney = FastMoney.from(myMoney);

money = Money.from(fastMoney);
fastMoney = FastMoney.from(money);

In the above example, as long as the scale of 5 is never exceeded, no implicit rounding is performed. Bigger scales require rounding, when creating new instances of FastMoney.

2.2.6. Other utility functions

The moneta reference implementation also provides implementations for several commonly used simple monetary functions in the org.javamoney.moneta.functions package:

  • MonetaryUtil.reciprocal() provides an operator for calculating the reciprocal value of an amount (1/amount).

  • MonetaryUtil.permil(BigDecimal decimal), MonetaryUtil.permil(Number number), MonetaryUtil.permil(Number number, MathContext mathContext) provides an operator for calculating permils.

  • MonetaryUtil.percent(BigDecimal decimal), MonetaryUtil.percent(Number number) provides an operator for calculating percentages.

  • MonetaryUtil.minorPart() provides an operator for extracting only the minor part of an amount.

  • MonetaryUtil.majorPart() provides an operator for extracting only the major part of an amount.

  • MonetaryUtil.minorUnits() provides a query for extracting only the minor units of an amount.

  • MonetaryUtil.majorUnits() provides a query for extracting only the major units of an amount.

Additionally several aggregate functions are provided on MonetaryFunctions, they are specially useful when combined with the new Java 8 Lambda/Streaming features:

  • public static Collector<MonetaryAmount, ?, Map<CurrencyUnit, List<MonetaryAmount>>> groupByCurrencyUnit() provides a Collector to group by CurrencyUnit.

  • public static Collector<MonetaryAmount, MonetarySummaryStatistics, MonetarySummaryStatistics> summarizingMonetary() create the summary of the MonetaryAmount.

  • public static Collector<MonetaryAmount, GroupMonetarySummaryStatistics, GroupMonetarySummaryStatistics> groupBySummarizingMonetary() create MonetaryAmount group by MonetarySummary.

  • public static Comparator<MonetaryAmount> sortCurrencyUnit() get a comparator for sorting currency units ascending.

  • public static Comparator<MonetaryAmount> sortCurrencyUnitDesc() get a comparator for sorting currency units descending.

  • public static Comparator<MonetaryAmount> sortNumber() + access a comparator for sorting amount by number value ascending.

  • public static Comparator<MonetaryAmount> sortNumberDesc() access a comparator for sorting amount by number value descending.

  • public static Predicate<MonetaryAmount> isCurrency(CurrencyUnit currencyUnit) creates a predicate that filters by CurrencyUnit.

  • public static Predicate<MonetaryAmount> isNotCurrency(CurrencyUnit currencyUnit) creates a predicate that filters by +CurrencyUnit.

  • public static Predicate<MonetaryAmount> containsCurrencies(CurrencyUnit requiredUnit, CurrencyUnit... otherUnits) creates a filtering predicate based on the given currencies.

  • public static Predicate<MonetaryAmount> isGreaterThan(MonetaryAmount amount) creates a filter using MonetaryAmount.isGreaterThan.

  • public static Predicate<MonetaryAmount> isGreaterThanOrEqualTo( MonetaryAmount amount) creates a filter using MonetaryAmount.isGreaterThanOrEqualTo.

  • public static Predicate<MonetaryAmount> isLessThan(MonetaryAmount amount) creates a filter using MonetaryAmount.isLess.

  • public static Predicate<MonetaryAmount> isLessThanOrEqualTo( MonetaryAmount amount) creates a filter using MonetaryAmount.isLessThanOrEqualTo.

  • public static Predicate<MonetaryAmount> isBetween(MonetaryAmount min, MonetaryAmount max) creates a filter using the isBetween predicate.

  • public static MonetaryAmount sum(MonetaryAmount a, MonetaryAmount b) adds two monetary together.

  • public static MonetaryAmount min(MonetaryAmount a, MonetaryAmount b) returns the smaller of two MonetaryAmount values. If the arguments have the same value, the result is that same value.

  • public static MonetaryAmount max(MonetaryAmount a, MonetaryAmount b) returns the greater of two MonetaryAmount values. If the arguments have the same value, the result is that same value.

  • public static BinaryOperator<MonetaryAmount> sum() Creates a BinaryOperator to sum.

  • public static BinaryOperator<MonetaryAmount> min() creates a BinaryOperator to calculate the minimum amount

  • public static BinaryOperator<MonetaryAmount> max() creates a BinaryOperator to calculate the maximum amount.

2.2.7. Performance Aspects

Performance was not measured in deep. Nevertheless we have a simple test in place, which is executed during all component test runs, which performs different monetary operations on the different implementation types provided:

Simple Performance Test Code
M money1 = money1.add(M.of(EURO, 1234567.3444));
money1 = money1.subtract(M.of(EURO, 232323));
money1 = money1.multiply(3.4);
money1 = money1.divide(5.456);
money1 = money1.with(Monetary.getRounding());

All tests were executed on a notebook with an Intel i7 2.6GHz processor with SSD. The VM was not configured in any special way.

This test is executed 100000 times for each monetary amount class M:

Performance Test Results for monetary arithmetic, no implementation mix
Duration for 100000 operations (Money,BD): 2107 ms (21 ns per loop) -> EUR 1657407.95
Duration for 100000 operations (FastMoney,long): 1011 ms (10 ns per loop) -> EUR 1657407.95000

The same test is also done, hereby mixing different implementation types. Also this test is executed 100000 times for each monetary amount class M:

Performance Test Results for monetary arithmetic, mixing implementations
Duration for 100000 operations (FastMoney/Money mixed): 899 ms (8 ns per loop) -> EUR 1657407.95000
Duration for 100000 operations (Money/FastMoney mixed): 1883 ms (18 ns per loop) -> EUR 1657407.95

2.3. Rounding

Moneta provides different roundings, all accessible from the MonetaryRoundings singleton.

2.3.1. Arithmetic Roundings

You can acquire instances of arithmetic roundings by passing the target scale and RoundingMode to be used within the RoundingQuery passed:

Access and apply arithmetic rounding.
MonetaryRounding rounding = Monetary.getRounding(
                               RoundingQueryBuilder.of().setScale(4).set(RoundingMode.HALF_UP).build());
MonetaryAmount amt = ...;
MonetaryAmount roundedAmount = amt.with(rounding);

2.3.2. Default Roundings

Also a default MonetaryRounding can be accessed, which basically falls back to the according default rounding based on the current amount instance to be rounded:

Access and apply default rounding.
MonetaryRounding rounding = Monetary.getDefaultRounding();
MonetaryAmount amt = ...;
MonetaryAmount roundedAmount = amt.with(rounding); // implicitly uses Monetary.getRounding(CurrencyUnit);

Also you can access the default rounding for a given CurrencyUnit. By default this will return an arithmetic rounding based on the currency’s default fraction digits, but it may also return a non standard rounding, where useful.

Access and apply default currency rounding.
CurrencyUnit currency = ...;
MonetaryRounding rounding = Monetary.getRounding(currency);
MonetaryAmount amt = ...;
MonetaryAmount roundedAmount = amt.with(rounding); // uses Monetary.getRounding(CurrencyUnit);

For Swiss Francs also a corresponding cash rounding is accessible. In Switzerland the smallest minor in cash are 5 Rappen, so everything must be rounded to minors dividable by 5. This rounding can be accessed by setting the cashRounding=true property, when accessing a currency rounding for CHF:

Access Swiss Francs Cash Rounding
MonetaryRounding rounding = Monetary.getRounding(
  RoundingQueryBuilder.of().setCurrency(Monetary.getCurrency("CHF")).set("cashRounding", true).build()
);
MonetaryAmount amt = ...;
MonetaryAmount roundedAmount = amt.with(rounding); // amount rounded in CHF cash rounding

2.3.3. Register your own Roundings

You can add additional roundings by registering instances of RoundingProviderSpi. Be default this has to be done based on the mechanism as defined by the Java ServiceLoader.

Implement a RoundingProviderSpi providing a currency rounding for "BTC" (Bitcoin)
public final class TestRoundingProvider implements RoundingProviderSpi {

    private static final MonetaryRounding ROUNDING = new MyCurrencyRounding();

    private final Set<String> roundingNames;

    public TestRoundingProvider() {
        Set<String> names = new HashSet<>();
        names.add("custom1");
        this.roundingNames = Collections.unmodifiableSet(names);
    }

    @Override
    public MonetaryRounding getRounding(RoundingQuery roundingQuery) {
        CurrencyUnit cu = roundingQuery.getCurrency();
        if (cu != null && "BTC".equals(cu.getCurrencyCode())) {
            return ROUNDING;
        }
        return null;
    }

    @Override
    public Set<String> getRoundingNames() {
        return Collections.emptySet();
    }

}

3. Currency Conversion

3.1. Basics

Basically converting of amounts into other currencies is based on the concept of MonetaryOperator, which transforms an amount into another amount (of the same implementation type). A conversion hereby is based on ExchangeRate that defines the transformation between amount A in currency Ca to amount B in currency Cb.

Hereby exchange rates can be accessed through an instanceof ExchangeRateProvider, which can be accessed from the MonetaryConversions singleton:

Access an ExchangeRateProvider and get an ExchangeRate
ExchangeRateProvider rateProvider = MonetaryConversions.getExchangeRateProvider("IMF");
ExchangeRate chfToUsdRate = rateProvider.getExchangeRate("CHF", "USD");

As you see above we can access a provider by passing its (unique) name. But we can also combine multiple providers to an compound provider, by passing a chain of provider names. This defines the chain of providers to be used to evaluate a rate required. By default, the first result returned by a provider in the chain is returned. So if we want to use the "ECB" provider first and only use the "IMF" provider for currencies not covered by the "ECB" provider we can write the following code:

Access a compound ExchangeRateProvider and get an ExchangeRate
ExchangeRateProvider rateProvider = MonetaryConversions.getExchangeRateProvider("ECB", "IMF");
ExchangeRate eurToChfRate = rateProvider.getExchangeRate("EUR", "CHF");

Finally we can also omit the definition of a provider chain. This will use the default provider chain:

Access an ExchangeRate using the default provider chain
ExchangeRateProvider rateProvider = MonetaryConversions.getExchangeRateProvider();
ExchangeRate eurToChfRate = rateProvider.getExchangeRate("EUR", "CHF");

3.1.1. Extracting a CurrencyConversion

A CurrencyConversion extends MonetaryOperator and is therefore directly applicable on every MonetaryAmount. Hereby a CurrencyConversion instance is always bound to a terminating currency and an underlying ExchangeRateProvider. As a consequence each ExchangeRateProvider allows to get a CurrencyConversion instance by passing the terminating currency:

Getting a CurrencyConversion from an ExchangeRateProvider
ExchangeRateProvider rateProvider = MonetaryConversions.getExchangeRateProvider();
CurrencyConversion conversion = rateProvider.getCurrencyConversion("CHF");

MonetaryAmount amountInUSD = ...;
MonetaryAmount amountInCHF = amountInUSD.with(conversion);

3.2. Exchange Rate Providers

Moneta provides quite powerful conversion providers, which allows you to perform currency conversion for most commonly used currencies, in some cases event back until 1995:

  • ECB connects to the online resources of the European Central Bank, which provides daily exchange rates related to EURO.

  • ECB-HIST90 connects the historic currencies feed of the European Central Bank, which provides exchange rates back for the last 90 days.

  • ECB-HIST connects the historic currencies feed of the European Central Bank, which provides exchange rates back until 1999.

  • IMF connects to the data-feed of the International Monetary Fund, which provides daily exchange rates for almost all important currencies. Hereby the IMF feeds are internally build up as derived rates, since IMF provides data using the intermediate SDR currency unit.

  • IDENT provides rates with a factor of 1.0, where base and target currency are the same.

By default the chain of rate providers is configured as IDENT,ECB,IMF,ECB-HIST,ECB-HIST90. As defined by the JSR the conversion provider chain can be configured in javamoney.properties as follows:

Overriding the conversion provider chain
#Currency Conversion
conversion.default-chain=IDENT,ECB,IMF,ECB-HIST,ECB-HIST90

3.2.1. Configuring the Exchange Rate Providers

The exchange rate providers provided provide several options to be configured, especially also the locations of data feeds and the (re)load/update settings:

Configuring the provided exchange rate providers
# ResourceLoader-Configuration (optional)
# ECB Rates
load.ECBCurrentRateProvider.type=SCHEDULED
load.ECBCurrentRateProvider.period=03:00
load.ECBCurrentRateProvider.resource=org/javamoney/moneta/convert/ecb/defaults/eurofxref-daily.xml
load.ECBCurrentRateProvider.urls=https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml

load.ECBHistoric90RateProvider.type=SCHEDULED
load.ECBHistoric90RateProvider.period=03:00
#load.ECBHistoric90RateProvider.at=12:00
load.ECBHistoric90RateProvider.resource=org/javamoney/moneta/convert/ecb/defaults/eurofxref-hist-90d.xml
load.ECBHistoric90RateProvider.urls=https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml

load.ECBHistoricRateProvider.type=SCHEDULED
load.ECBHistoricRateProvider.period=24:00
load.ECBHistoricRateProvider.delay=01:00
load.ECBHistoricRateProvider.at=07:00
load.ECBHistoricRateProvider.resource=org/javamoney/moneta/convert/ecb/defaults/eurofxref-hist.xml
load.ECBHistoricRateProvider.urls=https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml

# IMF Rates
load.IMFRateProvider.type=SCHEDULED
load.IMFRateProvider.period=06:00
#load.IMFRateProvider.delay=12:00
#load.IMFRateProvider.at=12:00
load.IMFRateProvider.resource=/java-money/defaults/IMF/rms_five.tsv
load.IMFRateProvider.urls=https://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y

4. Formatting Monetary Amounts

MonetaryAmountFormat instances can be accessed from the MonetaryFormats singleton. Similar to the Java platform, formats can be accessed by passing a country Locale. But JSR 354 also supports accessing formats by a (unique) name or even given a complex query, that allows to pass any number of parameters to configure the format to use. In contrast to DecimalFormat, the JSR 354 formats are thread-safe and immutable.

Accessing Amount Formats
MonetaryAmountFormat formatCountry = MonetaryFormats.getAmountFormat(Locale.GERMANY);
MonetaryAmountFormat formatNamed = MonetaryFormats.getAmountFormat("MyCustomFormat");
MonetaryAmountFormat formatQueried = MonetaryFormats.getAmountFormat(
  AmountFormatQueryBuilder.of("MyCustomFormat2")
    .set("strict", true)
    .set("omitNegative", true)
    .set("omitNegativeSign", "N/A")
    .build()
);

Given a MonetaryAmountFormat instance we can use it to format amounts:

MonetaryAmountFormat format = ...;
MonetaryAmount amount = ...;
String formattedString = format.format(amount);

Basically a MonetaryAmountFormat instance can also reverse the operation by parsing an amount back:

MonetaryAmountFormat format = ...;
String formattedString = ...;
MonetaryAmount amount = format.parse(formattedString);
Note
Be aware that parsing back an amount in a reverse operation may not always work. If a formatter implements only a unidirectional formatting operation, a MonetaryParseException will be thrown.

4.1. Customizing the Default Amount Formatters

Moneta basically provides similar formatting options to the one of DecimalFormat. It is possible to pass a DecimalFormat pattern string as a parameter for a Locale based format query:

MonetaryAmountFormat formatQueried = MonetaryFormats.getAmountFormat(
  AmountFormatQueryBuilder.of(Locale.GERMANY)
    .set(AmountFormatParams.PATTERN, "####,####")
    .build()
);

4.2. Registering your own Formats

You can add additional formats by registering instances of MonetaryAmountFormatProviderSpi. Be default this has to be done based on the mechanism as defined by the Java ServiceLoader.

Implement a MonetaryAmountFormatProviderSpi providing a format for "GKC" (GeeCoin)
public final class GeeCoinFormatProviderSpi implements MonetaryAmountFormatProviderSpi {

    private static final String PROVIDER_NAME = "GeeCoin";
    private static final String STYLE_NAME = "GeeCoin";

    /** The supported locales. */
    private Set<Locale> supportedSets = new HashSet<>();
    /** The provided formats, by name. */
    private Set<String> formatNames = new HashSet<>();

    public GeeCoinFormatProviderSpi() {
        supportedSets.add(Locale.CHINA);
        supportedSets = Collections.unmodifiableSet(supportedSets);
        formatNames.add("GeeCoin");
        formatNames = Collections.unmodifiableSet(formatNames);
    }

    /*
     * (non-Javadoc)
     * @see
     * javax.money.spi.MonetaryAmountFormatProviderSpi#getProviderName()
     */
    @Override
    public String getProviderName() {
        return PROVIDER_NAME;
    }

    /*
     * (non-Javadoc)
     * @see
     * javax.money.spi.MonetaryAmountFormatProviderSpi#getFormat(javax.money.format.AmountFormatContext)
     */
    @Override
    public Collection<MonetaryAmountFormat> getAmountFormats(AmountFormatQuery amountFormatQuery) {
        Objects.requireNonNull(amountFormatQuery, "AmountFormatContext required");
        if (!amountFormatQuery.getProviderNames().isEmpty()
            && !amountFormatQuery.getProviderNames().contains(getProviderName())) {
            return Collections.emptySet();
        }
        if (!(amountFormatQuery.getFormatName() == null
            || STYLE_NAME.equals(amountFormatQuery.getFormatName()))) {
            return Collections.emptySet();
        }
        AmountFormatContextBuilder builder = AmountFormatContextBuilder.of(PROVIDER_NAME);
        if (amountFormatQuery.getLocale() != null) {
            builder.setLocale(amountFormatQuery.getLocale());
        }
        builder.importContext(amountFormatQuery, false);
        builder.setMonetaryAmountFactory(amountFormatQuery.getMonetaryAmountFactory());
        return Arrays.asList(new MonetaryAmountFormat[]{new GeeCoinAmountFormat(builder.build())});
    }

    @Override
    public Set<Locale> getAvailableLocales() {
        return supportedSets;
    }

    @Override
    public Set<String> getAvailableFormatNames() {
        return formatNames;
    }

}

4.3. Overriding values in javamoney.properties

The reference implementation supports overriding of the values in javamoney.properties by prefixing the keys with a priority value in brackets. Hereby the mechanism reads all javamoney.properties resources visible on the classpath. If no priority is annotated, priority=0 is assumed:

Overriding a Configuration Value using a Priority
{100}myKey=myValue

If two entries have the same priority an exception is thrown.