Working With Currency Data In Java

Overview

Working with currency data in Java involves several key aspects: conversion rates, formatting, rounding, and arithmetic operations. This article will guide you through handling these aspects effectively in Java, ensuring accurate and reliable financial applications.

Conversion Rates

To handle currency conversion, you can use external APIs like the ExchangeRate-API. This API allows you to fetch exchange rates easily and integrate them into your Java project. Here’s a simple example using the ExchangeRate-API and Google’s GSON library to fetch exchange rates:

The exchange rate API response
    public record Rate (String code, Double rate) {
    }

    public record ExchangeResponse(
            String result,
            String documentation,
            String terms_of_use,
            long time_last_update_unix,
            String time_last_update_utc,
            long time_next_update_unix,
            String time_next_update_utc,
            String base_code,
            Map<String, Double> conversion_rates
    ) {
    }
    
    public static List<Rate> getRates(String currency) throws IOException, InterruptedException {
        var url = String.format("https://v6.exchangerate-api.com/v6/%s/latest/%s", Secrets.EXCHANGE_RATE_API_KEY, currency);

        //send GET request to obtain the exchange rate
        HttpClient client = HttpClient.newHttpClient();
        var response = client
                .send(
                        HttpRequest.newBuilder()
                                .uri(URI.create(url))
                                .build(),
                        HttpResponse.BodyHandlers.ofString()
                );

        //parse the conversion_rates in the response using gson and return

        var gson = new GsonBuilder().create();
        var exchangeResponse = gson.fromJson(response.body(), ExchangeResponse.class);
        return exchangeResponse.conversion_rates().entrySet().stream().map(e -> new Rate(e.getKey(), e.getValue())).toList();
    }

Now, we can run the function and get the response back:

    public static void main(String[] args) throws IOException, InterruptedException {
        var rates = getRates("USD");
        System.out.println(rates);
    }
Getting the exchange rate in java

Formatting

Formatting currency values according to locale is crucial for readability and correctness. Java provides the NumberFormat class for this purpose. Here’s how you can format a currency value for a specific locale:

 private static void printCurrencyWithLocales() {

        System.out.println("Currency with locales");
        var localeUs = new Locale.Builder().setLanguage("en").setRegion("US").build();
        var localeFr = new Locale.Builder().setLanguage("fr").setRegion("FR").build();
        var localeDe = new Locale.Builder().setLanguage("de").setRegion("DE").build();
        var localeCa = new Locale.Builder().setLanguage("fr").setRegion("CA").build();

        var localeList = List.of(
                localeUs,
                localeFr,
                localeDe,
                localeCa
        );

        double amount = 2500.01;

        for (var locale : localeList) {
            var currencyFormatter = NumberFormat.getCurrencyInstance(locale);
            System.out.println(currencyFormatter.format(amount));
        }

    }

This code when run will output the following:

Print money values with locales in java

Rounding

Rounding currency values is essential to avoid precision loss, especially when dealing with financial calculations. Java’s BigDecimal class provides methods for rounding values according to various rounding modes.

In Java, the scale of a BigDecimal represents the number of digits to the right of the decimal point. The scale can be zero or positive, and if it’s negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale. For example, a BigDecimal with a scale of 3 means the number has three digits after the decimal point.

Let’s consider some rounding examples:

    public static void roundingWithBigDecimal() {
        var moneyValue1 = "1.6362";
        //demonstrate the rounding modes for BigDecimal
        //rounding up
        var v1 =  new BigDecimal(moneyValue1);
        var v1RoundedHalfUp = v1.setScale(3, RoundingMode.HALF_UP);
        var v1RoundedUp = v1.setScale(3, RoundingMode.UP);
        var v1RoundedCeiling = v1.setScale(3, RoundingMode.CEILING);

        System.out.printf("original value %s with rounding mode %s is %s%n", moneyValue1, RoundingMode.HALF_UP, v1RoundedHalfUp);
        System.out.printf("original value %s with rounding mode %s is %s%n", moneyValue1, RoundingMode.UP, v1RoundedUp);
        System.out.printf("original value %s with rounding mode %s is %s%n", moneyValue1, RoundingMode.CEILING, v1RoundedCeiling);
    }

This code outputs:

original value 1.6362 with rounding mode HALF_UP is 1.636
original value 1.6362 with rounding mode UP is 1.637
original value 1.6362 with rounding mode CEILING is 1.637
BigDecimal Rounding modes

CEILING vs UP

  • RoundingMode.UP: This mode rounds away from zero. For positive numbers, it behaves like rounding towards the higher number, and for negative numbers, it rounds towards a more negative number (i.e., away from zero in both cases).
  • RoundingMode.CEILING: This mode rounds towards positive infinity. For positive numbers, it behaves similarly to RoundingMode.UP, rounding towards the higher number. However, for negative numbers, it rounds towards zero, which is different from RoundingMode.UP.

Let’s consider the following example:

private static void ceilingVsUp () {
        BigDecimal positiveValue = new BigDecimal("2.3");
        BigDecimal negativeValue = new BigDecimal("-2.3");

        // Rounding up
        BigDecimal roundedUpPositive = positiveValue.setScale(0, RoundingMode.UP);
        BigDecimal roundedUpNegative = negativeValue.setScale(0, RoundingMode.UP);

        // Rounding to the ceiling
        BigDecimal roundedCeilingPositive = positiveValue.setScale(0, RoundingMode.CEILING);
        BigDecimal roundedCeilingNegative = negativeValue.setScale(0, RoundingMode.CEILING);

        System.out.println("Original: " + positiveValue);
        System.out.println("Rounded UP (Positive): " + roundedUpPositive);
        System.out.println("Rounded CEILING (Positive): " + roundedCeilingPositive);

        System.out.println("\nOriginal: " + negativeValue);
        System.out.println("Rounded UP (Negative): " + roundedUpNegative);
        System.out.println("Rounded CEILING (Negative): " + roundedCeilingNegative);
    }

This will output:

Original: 2.3
Rounded UP (Positive): 3
Rounded CEILING (Positive): 3

Original: -2.3
Rounded UP (Negative): -3
Rounded CEILING (Negative): -2
Up rounding vs Ceiling Rounding

Arithmetic Operations

For accurate arithmetic operations with currency values, it’s recommended to use the BigDecimal class, which provides methods for addition, subtraction, multiplication, and division without losing precision. Here’s an example:

private static void arithmeticWithBigDecimal() {
        BigDecimal value1 = new BigDecimal("100.05");
        BigDecimal value2 = new BigDecimal("50.06");

// Addition
        BigDecimal sum = value1.add(value2);

// Subtraction
        BigDecimal difference = value1.subtract(value2);

// Multiplication
        BigDecimal product = value1.multiply(value2);

// Division
        BigDecimal quotient = value1.divide(value2, 20, RoundingMode.HALF_UP);

        System.out.println("Sum: " + sum);
        System.out.println("Difference: " + difference);
        System.out.println("Product: " + product);
        System.out.println("Quotient: " + quotient);
    }

 private static void arithmeticWithoutBigDecimal() {
        double value1 = Double.parseDouble("100.05");
        double value2 = Double.parseDouble("50.06");
        System.out.println("Sum: " + (value1 + value2));
        System.out.println("Difference: " + (value1 - value2));
        System.out.println("Product: " + (value1 * value2));
        System.out.println("Quotient: " + (value1 / value2));
    }

Here is the output:

=== Arithmetic with BigDecimal
Sum: 150.11
Difference: 49.99
Product: 5008.5030
Quotient: 1.99860167798641630044
=== Arithmetic without BigDecimal
Sum: 150.11
Difference: 49.989999999999995
Product: 5008.503
Quotient: 1.9986016779864162
Arithmetic with and without BigDecimal

As you can see, without using BigDecimal, there is an unexpected result in the subtract operation.

Conclusion

In conclusion, handling currency data in Java requires careful consideration of conversion rates, formatting, rounding, and arithmetic operations. By leveraging APIs like ExchangeRate-API for conversion rates, utilizing the NumberFormat class for formatting, employing the BigDecimal class for rounding and arithmetic operations, you can ensure your Java applications handle currency data accurately and efficiently.

Leave a Comment