Skip links

Modeling money in Java: Pitfalls and Solutions

Jump To Section

Infosec, Data Engineering, Quality Engineering
Modeling money in Java

Introduction

For Java programmers, dealing with money representation is likely a common task. It’s surprising that there isn’t a standardized method for handling monetary values, but this lack of uniformity can present significant challenges and pitfalls. In this article, we’ll explore the challenges arising from the absence of standardization in handling currency in Java, examining common issues and proposing effective solutions.

Problems with operations on monetary values

If your application operates on a single currency and is primarily focused on “displaying” and storing money rather than on performing financial calculations, then maybe you’re in the clear. 

However, you still encounter issues such as:

  • Floating-Point Errors
  • Formatting for Presentation
  • Minor Units
  • Mixing Different Currencies
  • Rounding Errors

Let’s delve into these issues one by one.

Floating-Point Errors

Not all real numbers can be accurately represented in a floating-point format using binary representation. The truth is that even some simple operations can result in a loss of precision. 

Here are some examples:

double val = 1.00;
        for (int i = 0; i < 10; i++) {
            val += 0.10;
        }
        System.out.println(val); // 2.000000000000001

        double first = 0.1;
        double second = 0.2;
        double result = first + second;
        System.out.println(result); // 0.30000000000000004        double a = 1.0000001;
        double b = 2.0000002;
        double c = 3.0000003;        System.out.println((a + b) + c); // 6.0000006
        System.out.println(a + (b + c)); // 6.000000600000001

In Java, float and double data types should never be used for precise values, such as currency. For such operations, it’s a better idea to use BigDecimal class:

BigDecimal val = new BigDecimal("1.00");
        for (int i = 0; i < 10; i++) {
            val = val.add(new BigDecimal("0.10"));
        }
        System.out.println(val); // 2.00

        BigDecimal first = new BigDecimal("0.1");
        BigDecimal second = new BigDecimal("0.2");
        BigDecimal result = first.add(second);
        System.out.println(result); // 0.3        BigDecimal a = new BigDecimal("1.0000001");
        BigDecimal b = new BigDecimal("2.0000002");
        BigDecimal c = new BigDecimal("3.0000003");        System.out.println((a.add(b)).add(c)); // 6.0000006
        System.out.println(a.add(b.add(c))); // 6.0000006

Now as you can see, the results are correct.

You may wonder why I am using BigDecimal(String) constructor – well, this is another trap. As you can read in the documentation of this constructor:

[…] This is generally the preferred way to convert a float or double into a BigDecimal, as it doesn’t suffer from the unpredictability of the BigDecimal(double) constructor.

System.out.println(new BigDecimal(0.35)); // 0.34999999999999997779553950749686919152736663818359375
System.out.println(new BigDecimal("0.35")); // 0.35

If you want to read more about this problem, you can take a look at the IEEE Standard for Floating-Point Arithmetic (IEEE 754), which defines the way to transform real numbers into a binary format.

You could also use long data type and work on currency minor units, like using 100 (cents) to represent one dollar and make a rounding to a currency minor unit at the end of calculations.

Formatting for Presentation

The same amount can be formatted differently based on a currency and user’s locale. You should take into consideration – currency symbol location (before/after amount), grouping, decimal separator, user’s locale etc.

BigDecimal amount = new BigDecimal("15000.99");
        NumberFormat usFormat = NumberFormat.getCurrencyInstance(Locale.US);
        System.out.println(usFormat.format(amount)); // $15,000.99

        NumberFormat franceFormat = NumberFormat.getCurrencyInstance(Locale.FRANCE);
        System.out.println(franceFormat.format(amount)); // 15 000,99 €

Minor Units

Most currencies have two decimals, but some currencies do not have decimals at all (like JPY), and some have three decimals (like BHD).

Mixing Different Currencies in some operations

Quite self-explanatory, but it’s possible that one would forget to compare currencies and focus only on operations on numbers.

Rounding Errors

Some currencies may have their own rounding rules, like CHF, where prices must be rounded to 5 Rappen. For example: values between CHF 0,975 and CHF 1,024 will be rounded to CHF 1,00.

Money Pattern

At this point you are aware of some pitfalls and must look for a solution. In huge and complex applications, if there’s no unified approach to money representation, there is a chance that sooner or later someone will forget to use BigDecimal, or to check currency. That’s why Money Pattern has been introduced.

You can find Money Pattern described in “Patterns of Enterprise Application Architecture” written by Martin Fowler. It advocates for encapsulating currency and amount together as an object. This pattern promotes the use of a custom class to represent monetary values, incorporating information about the currency alongside the amount. 

This not only ensures precision but also enhances code readability and maintainability. It describes how to create a class representing monetary amount, which will take care of currency verification, rounding, formatting, comparison, and some other utility methods.

public class Money {
    private BigDecimal amount; // or long if you want to operate on minor units
    private Currency currency;

    // methods like add/substract/multiply/allocate
    // utility methods for comparison: eq, gt, gte, lt, lte ...

    // currency check in every method
    // formatting logic
}

One can design such class’ on their own, or use existing libraries, like joda-money or JSR 354 API implementation — Moneta. Moneta library (JSR 354) was even supposed to be added to a JDK! It also has a nice feature of adding new currencies, like bitcoins or loyalty/reward points. Additionally, you can define your own logic for exchange rates, rounding etc.

If you want to read more about Moneta Library you can check the User Guide or this Baeldung article. Below is a sample code from the user guide:

FastMoney m = FastMoney.of(200.20, "USD");

Money m2 = Money.of(200.20, "USD");

Moneta Library defines MonetaryAmount interface, which has different implementations like Money (using BigDecimal) and FastMoney (optimised for speed, representing a monetary amount only as an integral number using a number scale of 100 000).

Conclusion

Modeling money in Java requires careful consideration of data types and precision. By adopting the “Money” pattern, as proposed by Martin Fowler, and leveraging specialised libraries, developers can navigate these challenges seamlessly. 

However, one must be aware of other potential issues:

  • Choosing a correct data type for storing money in a database.
  • Ensuring that other microservices within the system (both frontend and backend) and the developers are aware of these issues and adhere to the same “standard” when handling monetary amounts.
Picture of Aleksander Kolata

Aleksander Kolata

Subscribe

Suggested Reading

Ready to Unlock Your Enterprise's Full Potential?