Most of Enterprise projects are about money. And most of them implement an API.

API implements the way the datum are migrating from one application to another one. The money consists of an amount and a currency. The amount may be fractional.

So this post is about how to deal with fractional amount of money when implementing an API in Java.

It’s not a secret that fractional numbers in computer world bring pains to developers because of:

  • fractional numbers have precisions
  • they have more than one representation

Fractional numbers in Java may be stored in variables of types:

  1. float
  2. String
  3. BigDecimal

Let’s discuss peculiar properties of each type.

Float

It is for math only. Its precision is floating (link #1). It is not applicable for money.

String

Strings are well representative but are not for calculation. You cannot multiply or subtract strings.

BigDecimal

It is intended for storing money but this type has its own peculiar properties which must be known by developers. Let’s take the spotlight on them.

Instantiating BigDecimal

with integer number
BigDecimal amount = new BigDecimal(5); System.out.println(amount);
Code language: Java (java)

The result is precisely that what intended.

> Task :BigDecimalExample.main() 5
Code language: CSS (css)
with float number
BigDecimal amount = new BigDecimal(5.13);
Code language: Java (java)

It is strongly recommended not to use this kind of instantiating of BigDecimal objects because of the result of such operation may be something unpredictable.

Live example:

BigDecimal amount = new BigDecimal(5.123456789); System.out.println(amount);
Code language: Java (java)

The result is discouraging.

> Task :BigDecimalExample.main() 5.12345678899999956712463244912214577198028564453125
Code language: CSS (css)

The result is not the same as being in code. This behavior is inappropriate dealing with money.

with String
BigDecimal amount = new BigDecimal("5.13");
Code language: Java (java)

What can we say about this approach?

It is good if the source is integer.

BigDecimal amount = new BigDecimal("5"); System.out.println(amount);
Code language: Java (java)
> Task :BigDecimalExample.main() 5
Code language: CSS (css)

It is also good with floating numbers.

import java.math.BigDecimal; public class BigDecimalExample { public static void main(String[] args) { BigDecimal amount = new BigDecimal("5.123456789"); System.out.println(amount); } }
Code language: Kotlin (kotlin)
> Task :BigDecimalExample.main() 5.123456789
Code language: CSS (css)

But how to deal with it in APIs? Let’s go ahead.

Few words about APIs

API is the Application Programming Interface – the description how one application provides its datum for any other application. As the most of applications go to Web last years, the most used protocols used in API implementation are:

  1. JSONthe most commonly used
  2. XML
  3. GraphQL (JSON based)
  4. Protobuf
  5. GSON

We will discuss JSON further as the most commonly used.

Simple JSON has few types. They are only: string, number. Therefore any library reading JSON will face with only two types of number fields: strings, numbers. And how BigDecimal reads numbers from these types? Sure, painfull.

The developer’s debt is to build the System which will deal with incoming and provide outgoing numbers properly, don’t breaking the precision. The best choice is to send numbers as strings every time. But it is less possible, especially because of a person as a developer of one System is not responsible of the other System which is not developed by this person.

The Approach

The following approach is good not for Java only and for any programming language as well. The main hint is hidden in Currency. Currency is a common dictionary with common properties. And the crucial property is the amount of digits after the dot/comma separating integer part of the number from the fractional one.

In USD there are 2 digits in fractional part. In EUR there are 2 digits as well. And Tunisian Dinar has 3 digits in fractional part. (link #2)

Java type Currency has appropriate property called defaultFractionDigits.

Currency eur = Currency.getInstance("EUR"); System.out.println("EUR: " + eur.getDefaultFractionDigits()); Currency tnd = Currency.getInstance("TND"); System.out.println("TND: " + tnd.getDefaultFractionDigits());
Code language: Java (java)
> Task :BigDecimalExample.main() EUR: 2 TND: 3
Code language: CSS (css)

Additionally, BigDecimal type in Java has the possibility to move point left or right.

BigDecimal amount = new BigDecimal("4.12"); System.out.println(amount); amount = amount.movePointRight(2); System.out.println(amount); amount = amount.movePointLeft(2); System.out.println(amount);
Code language: Java (java)
> Task :BigDecimalExample.main() 4.12 412 4.12
Code language: CSS (css)

This possibility gives us the opportunity to eliminate the issue with loosing precision while creating BigDecimal from float number.

The approach is to keep the amount and the corresponding currency together into API message and represent every money amount in Minor Units (USD in cents, i.e.).

The instance of such approach in Java is the following.

MoneyDto keeps information about money amount and currency together.

import java.math.BigDecimal; import java.util.Currency; public class MoneyDto { private final BigDecimal amount; private final Currency currency; public static MoneyDto fromMinor(BigDecimal amount, Currency currency) { return new MoneyDto(amount.movePointLeft(currency.getDefaultFractionDigits()), currency); } public static MoneyDto fromJson(String amount, String cur) { Currency currency = Currency.getInstance(cur); return new MoneyDto(new BigDecimal(amount).movePointLeft(currency.getDefaultFractionDigits()), currency); } public MoneyDto(BigDecimal amount, Currency currency) { this.amount = amount; this.currency = currency; } public BigDecimal getAmount() { return amount; } public Currency getCurrency() { return currency; } @Override public String toString() { return "{" + "\"amount\":" + amount.movePointRight(currency.getDefaultFractionDigits()) + ", \"currency\":\"" + currency + "\"" + '}'; } }
Code language: Java (java)

When this object is created it is represented in Minor Units of the currency provided.

BigDecimal amount = new BigDecimal("4.12"); Currency eur = Currency.getInstance("EUR"); System.out.println(amount + " " + eur); MoneyDto moneyDto = new MoneyDto(amount, eur); System.out.println(moneyDto);
Code language: Java (java)
> Task :BigDecimalExample.main() 4.12 EUR {"amount":412, "currency":"EUR"}
Code language: JavaScript (javascript)

When this object is creating from JSON it is well performed.

MoneyDto incomingUsd = MoneyDto.fromJson("514", "USD"); System.out.println(incomingUsd + " -> " + incomingUsd.getAmount() + " " + incomingUsd.getCurrency()); MoneyDto incomingTnd = MoneyDto.fromJson("5148", "TND"); System.out.println(incomingTnd + " -> " + incomingTnd.getAmount() + " " + incomingTnd.getCurrency());
Code language: Java (java)
{"amount":514, "currency":"USD"} -> 5.14 USD {"amount":5148, "currency":"USD"} -> 5.148 TND
Code language: JavaScript (javascript)

How to show such Money object to user properly?

There are no issue with representing BigDecimal when incoming number is well prepared.

BigDecimal amount = new BigDecimal("4.12"); System.out.println(amount);
Code language: Java (java)
> Task :BigDecimalExample.main() 4.12
Code language: CSS (css)

The issue is facing when incoming number is badly prepared.

BigDecimal amount = new BigDecimal("4.120000"); System.out.println(amount);
Code language: Java (java)
> Task :BigDecimalExample.main() 4.120000
Code language: CSS (css)

What are the tailing zeros???

Keep calm, the solution is here.

import java.math.BigDecimal; import java.text.DecimalFormat; import java.util.Currency; public class NumberFormatHelper { public static String currencyAmount(BigDecimal amount, Currency currency) { final DecimalFormat df = new DecimalFormat("0"); df.setMaximumFractionDigits(currency.getDefaultFractionDigits()); return df.format(amount); } }
Code language: JavaScript (javascript)
BigDecimal amount2 = new BigDecimal("4.120000"); Currency eur = Currency.getInstance("EUR"); System.out.println(NumberFormatHelper.currencyAmount(amount2, eur));
Code language: JavaScript (javascript)
> Task :BigDecimalExample.main() 4.12
Code language: CSS (css)

Official documentation to DecimalFormat is here. (link #3)

Sources

The sources are available on my Git, all the steps are shown in history.


  1. IEEE_754 standard for floating-point arithmetic
  2. ISO 4217 – alpha codes of currencies
  3. Official documentation to DecimalFormat
How to deal with Money model when constructing API in Java
Tagged on:             

One thought on “How to deal with Money model when constructing API in Java

Leave a Reply

Your email address will not be published. Required fields are marked *