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

Latest Reads

Subscribe

Suggested Reading

Ready to Unlock Your Enterprise's Full Potential?

Adaptive Clinical Trial Designs: Modify trials based on interim results for faster identification of effective drugs.Identify effective drugs faster with data analytics and machine learning algorithms to analyze interim trial results and modify.
Real-World Evidence (RWE) Integration: Supplement trial data with real-world insights for drug effectiveness and safety.Supplement trial data with real-world insights for drug effectiveness and safety.
Biomarker Identification and Validation: Validate biomarkers predicting treatment response for targeted therapies.Utilize bioinformatics and computational biology to validate biomarkers predicting treatment response for targeted therapies.
Collaborative Clinical Research Networks: Establish networks for better patient recruitment and data sharing.Leverage cloud-based platforms and collaborative software to establish networks for better patient recruitment and data sharing.
Master Protocols and Basket Trials: Evaluate multiple drugs in one trial for efficient drug development.Implement electronic data capture systems and digital platforms to efficiently manage and evaluate multiple drugs or drug combinations within a single trial, enabling more streamlined drug development
Remote and Decentralized Trials: Embrace virtual trials for broader patient participation.Embrace telemedicine, virtual monitoring, and digital health tools to conduct remote and decentralized trials, allowing patients to participate from home and reducing the need for frequent in-person visits
Patient-Centric Trials: Design trials with patient needs in mind for better recruitment and retention.Develop patient-centric mobile apps and web portals that provide trial information, virtual support groups, and patient-reported outcome tracking to enhance patient engagement, recruitment, and retention
Regulatory Engagement and Expedited Review Pathways: Engage regulators early for faster approvals.Utilize digital communication tools to engage regulatory agencies early in the drug development process, enabling faster feedback and exploration of expedited review pathways for accelerated approvals
Companion Diagnostics Development: Develop diagnostics for targeted recruitment and personalized treatment.Implement bioinformatics and genomics technologies to develop companion diagnostics that can identify patient subpopulations likely to benefit from the drug, aiding in targeted recruitment and personalized treatment
Data Standardization and Interoperability: Ensure seamless data exchange among research sites.Utilize interoperable electronic health record systems and health data standards to ensure seamless data exchange among different research sites, promoting efficient data aggregation and analysis
Use of AI and Predictive Analytics: Apply AI for drug candidate identification and data analysis.Leverage AI algorithms and predictive analytics to analyze large datasets, identify potential drug candidates, optimize trial designs, and predict treatment outcomes, accelerating the drug development process
R&D Investments: Improve the drug or expand indicationsUtilize computational modelling and simulation techniques to accelerate drug discovery and optimize drug development processes