Money in Java

Sai Nay Thurein Nyo
4 min readJun 15, 2021

--

If you are developing a financial application with multiple currency, consider using Money representation. Every amount will be associated with Currency so that we can prevent mis-calculation of financial amounts of different currency.

Basically, we want to have arithmetic operations. Currency rounding, currency conversion. When accidentally compute two different currencies, throw exception.

API. There are two popular apis. Moneta ( http://javamoney.github.io/ ) and Joda Money( https://www.joda.org/joda-money/ ).
After doing a comparison. I decided to use Joda Money

Moneta vs Joda Money

Let’s explore how to use Joda Money in our application. We want to achieve a few things:

  • Convert Money to JSON and vice-versa
  • Use Money in JPA @Entity objects and Spring Repository @Query
  • Understand BigMoney vs Money

Let’s get Started

Joda Money in Spring Boot application with JPA / Hibernate.
Sample code project: https://gitlab.com/sntrnyo/joda-money

Download / Clone the git repository, start spring boot application using STS or mvn spring-boot:run in project folder

The sample code expose REST API endpoints. To keep it simplified, I skipped the Service / Business Logic layer where we handle computations, transaction management etc.. in this project. Swagger 2 UI is enabled to see all available APIs. Go to http://localhost:8080/swagger-ui.html#/

Convert Money to Json and vice-versa

When Money object convert to JSON, we want it to be in following format which is more readable.

{
"amount": 12000.99,
"currency": "USD",
"str": "USD 12,000.99"
}

When parsing JSON to Money object, the input JSON should be as following:

{
"amount": 12000.99,
"currency": "USD"
}

Implementation

We need to create a Custom Json Conversion Module. Register it to ObjectMapper.

see sntrnyo.joda.money.json package for creating JodaMoneyModule.

The module can be registered to ObjectMapper in @SpringBootApplication class.

@Autowired
public void configureObjectMapper(final ObjectMapper mapper) {
mapper
.registerModule(new JodaMoneyModule())
.findAndRegisterModules();
}

You can try sample conversion in sample endpoints: @see MoneyEndpoint.java

GET http://localhost:8080/api/money/sample
RESPONE:
{
"amount": 16091.33,
"currency": "USD",
"str": "USD 16,091.33"
}
POST http://localhost:8080/api/money/sample
REQUEST:
{
"currency": "USD",
"amount": "12000.99"
}
RESPONSE:
{
"amount": 12000.99,
"currency": "USD",
"str": "USD 12,000.99"
}

Use Money in @Entity class

Store in two columns one for currency and one for amount. Create getter setter for Money type. Do not create getter setter for Currency String and BigDecimal amount.

Then in AccountRepository you can do some JPQL for the two fields. However, do note that there is no validation for mixing different currency in SQL level.

Then let’s try creating an account

Retrieve all accounts

So what is the beauty, try depositing USD 100 to the accounts. You should see the currency mis match exception. Also try creating JPY account and see what is the difference. There is no decimal place in JPY and it is already handled by Money.

Understanding Money vs BigMoney

Money honour the scale of the currency. Meaning you cannot create USD with 3 decimal place. Because USD scale is only 2 decimal. It will throw exception.

Money m = Money.of(CurrencyUnit.USD, new BigDecimal("100.123"));java.lang.ArithmeticException: Scale of amount 100.123 is greater than the scale of the currency USD

To overcome this, you can pass in the RoundingMode

Money m = Money.of(CurrencyUnit.USD, new BigDecimal("100.123"),RoundingMode.DOWN);

Any operation ( +-*/ ) with an amount which exceeds scale, there will be exception. So you have to pass in the Rounding Mode

Money m = m.plus(new BigDecimal("123.456"));
java.lang.ArithmeticException: Rounding necessary

BigMoney the scale is not affected. You can create the BigMoney of USD 100.123 without exception. But normally we will use Money because we want to keep the scale of the currency in application.

When to use BigMoney / Money

Before that, let’s see the interest rate computation exercise. In Money

BigDecimal factor = new BigDecimal("1.34678");
Money principle = Money.of(CurrencyUnit.USD, new BigDecimal("1"));
Money m = Money.zero(CurrencyUnit.USD);
for(int i =0;i<1000; i++)
{
m = m.plus(principle.multipliedBy(factor, RoundingMode.DOWN));
}
log.debug("M: {}",m.getAmount());
// RESULT >> M: 1340.00

In BigMoney

BigDecimal factor = new BigDecimal("1.34678");
BigMoney principle = BigMoney.of(CurrencyUnit.USD, new BigDecimal("1"));
BigMoney m = BigMoney.zero(CurrencyUnit.USD);
for(int i =0;i<1000; i++)
{
m = iim.plus(principle.multipliedBy(factor));
}
logger.debug("M: {}",m.getAmount());// RESULT >> IIM: 1346.78000

Using Money, there will be a difference of 6.78. Because we need to Round it down.

The amount got rounded to 2 decimals before adding. Using Money data type for rates calculation may have error in calculation, if the business case is to sum all the interest and then perform rounding.

Currency Conversion

Money usd = Money.parse("USD 150");BigDecimal rate = new BigDecimal("111.45");
Money jpy = usd.convertedTo(CurrencyUnit.JPY, rate , RoundingMode.DOWN);
logger.debug("jpy: {}", jpy.getAmount());
// RESULT >> jpy: 16717
BigMoney busd = BigMoney.parse("USD 150");
BigMoney bjpy = busd.convertedTo(CurrencyUnit.JPY, rate);
logger.debug("bjpy: {}", bjpy.getAmount());
// RESULT >> bjpy: 16717.50

We may be discussing more about Currency Converters using FixerAPI and Joda Money in future articles. Stay Tuned. And congratulation, you have reached to the end of the article. Hope to hear your thoughts on this. Please leave some comment.

--

--

No responses yet