Implementing a reliable library for currency conversion

Roberta Huang, associate backend engineer, and her team built a Java library to provide exact currency conversion with reliability and short response times.

GetYourGuide Tech Blog
GetYourGuide Tech Blog
8 min readJun 15, 2021

--

This story was originally posted in our main blog. To see it follow here.

Roberta Huang is an associate backend engineer in the Fintech team. The team focuses on both customers and supporting the internal teams. For customers, they create seamless checkout experiences. To help other teams shine, they build and scale financial services. Roberta shares how her team built a reliable Java library for currency conversions and the challenges they faced while developing it.

GetYourGuide offers a marketplace with thousands of activities provided by partners around the world. When partners create their activities, they set prices in their local currency. To make customers feel at home while they browse, we display activity prices in the customers’ preferred currency. Our platform currently supports 40 different currencies.

Activity cards displaying prices converted into customer’s preferred currency.
Activity cards displaying prices converted into customer’s preferred currency.

As we migrate from our monolithic architecture to a microservice architecture, the new services that support different currencies need a library that exposes the currency conversion logic. In the FinTech team, we have recently implemented such a library in Java — our FX SDK. The key requirements were for it to be fast, numerically consistent, and reliable.

Currency conversion is a simple multiplication (initial amount × exchange rate) from a mathematical perspective. Yet, it can raise different challenges from an implementation point of view. Let’s now dive into our development process of the FX SDK and the problems we faced while implementing it.

How are currency conversions computed?

The currency conversion logic is currently implemented in our monolith application, where we also store currencies and exchange rates data. We periodically fetch new data from our exchange rates providers and update our database with the latest exchange rates, stored with a certain precision. Whenever we convert an amount from a source currency to a target currency (for example, from 150.09€ to Japanese Yen, ¥), we perform the following steps:

  1. Pull the currently applicable exchange rate from the database (e.g. 1€ = ¥123.590492)
  2. Compute the raw converted amount into target currency (in our example, 150.09€ × 123.590492 = ¥18549.69694428)
  3. Round the converted amount based on the target currency. For example, in the case of Japanese Yen, there are no fractions of a Yen, so the final converted amount is rounded to ¥18550.

What is the library’s interface?

The FX SDK’s interface is simple. To convert prices between different currencies, the clients need to work with monetary amounts and currencies. They need to have a function that performs the currency conversion. Therefore, we provide two client-facing domain classes:

The following code snippets do not fully reflect our actual implementation and are simplified for understandability.

And finally, we also provide a Converter class with the currency conversion method:

What were the challenges and how did we solve them?

As we have seen previously, currency conversion is a critical feature for many areas of our product, so its availability and accuracy are crucial. Therefore, we need to guarantee both numerical precision and system reliability to assure positive customer experience and financial correctness.

🔢 Numerical representation with BigDecimal In Java

The first challenge for currency conversion is related to limitations on the numerical representation in computers. Real numbers are often stored using the floating-point representation. Because computers are finite-state machines, we cannot store all real numbers with arbitrary precision; some are rounded to the nearest representable floating-point number.

This means that we cannot compute currency conversions precisely with the floating-point representation, and the converted amounts would have a calculation error. Even though the error is relatively small and in most cases not noticeable in the converted amount after rounding, it becomes significant in magnitude when we deal with large values, and we still want to prevent it. Moreover, the errors can accumulate over mathematical operations, such as additions and multiplications.

An example of how a number stored with floating-points representation can differ from its real value.

Therefore, floating-point representation is not ideal for calculations with money, such as addition, subtraction, multiplication, VAT calculations or percentage discounts.

Fortunately, Java provides a way to represent decimal numbers more accurately with BigDecimal. BigDecimal receives a number as a string, and stores the value as an arbitrary precision integer unscaled value and a 32-bit integer scale (unscaledValue × 10^(-scale). Using BigDecimal, not only can we represent monetary amounts and exchange rates exactly, but we can also perform safe and accurate computations. With BigDecimal we have a precision of up to 10^(-(2³²)), which covers all needs that arise when working with money.

The smallest unit of a commonly used currency would probably occur for Bitcoin at 10^-8. The common operations of adding, subtracting and multiplying by an integer cannot increase the number of relevant decimals. Performing VAT calculations or applying percentage discounts can actually result in an increase in decimals, but there is always a clear bound — no one issues a 7.379842331467899% discount, and no government has such a definition of VAT. As the results of calculations need to be represented as meaningful amounts in the respective currency, subsequent rounding ensures that the decimals are reduced appropriately again.

conversion library Java FX SDK2.jpg

For example, if a customer buys a ticket with a normal price of €10.35 that is 10% off, the final price could be calculated as 10.35 * 0.9 = 9.315, which is not a valid Euro amount as it contains half a cent. In this situation, rounding needs to occur and this might create or destroy cents.

conversion library Java FX SDK.jpg

It is important to understand that in this scenario, the provided discount should not be computed as 10.35 * 0.1, but rather the original price minus the discounted price, which requires no rounding and therefore enforces consistency without the possibility of creating or destroying cents.

💪 Resilience: How we prevented network delays and data inconsistencies

As mentioned above, currencies and exchange rates data are stored in our monolith. A simple approach to fetch the data needed for currency conversion is via Remote Procedure Call, by calling an endpoint for each conversion. However, in this way, the currency conversions strongly depend on the availability of the monolith application.

Another downside is that the network delays would not meet our requirements, as they would make conversions too time-consuming. Instead, to synchronize the latest exchange rates, we use Apache Kafka as a messaging system to deliver the most recent data to all clients. Kafka is built for resilience: it provides replication, fault-tolerance guarantees, and zero downtime, which are essential for providing correct and up-to-date exchange rates for our use case.

Whenever we update the exchange rates in the monolith, we publish them to a specific Kafka topic. During the process, we make use of the outbox pattern with Debezium, such that we record the new Kafka message as part of the database transaction that updates the exchange rates, guaranteeing consistency between database updates and Kafka messages being produced.

conversion library Java FX SDK .jpg

💲Supporting new currencies

In case we have to support a new currency, we want to make it available immediately to all clients using the FX SDK. If we hard code currencies data (e.g. their symbol and rounding logic) in the library, and if we were to introduce a new currency, then we would have to update the SDK and make all clients upgrade the library to the newest version. This is not ideal. We choose to provide all currency data (ISO code, symbol and rounding logic) in the Kafka messages, along with the latest exchange rates. In this way, the clients can access the new currency right away and an update of the SDK is not required.

Kafka message produced by the monolith with up-to-date currencies and exchange rates data.

✏️ Topic catch up

For our Kafka topic, we set up log compaction to automatically delete old exchange rates messages. While it guarantees that we have at least the latest message in the queue, it could still contain some old exchange rates data. To prevent the FX SDK from using outdated exchange rates for currency conversions, we implemented a monitoring system that emits an event when it reaches the latest offset in Kafka. The client can then use the event to assess if the FX SDK has consumed the latest exchange rates and is ready to compute currency conversions.

🔒 Transaction-safe operations

Sometimes we have to compute multiple currency conversions in a single job. An example is the search results page, where we display a list of relevant activities with their prices in the customer’s currency. Since the FX SDK listens to Kafka and continuously updates itself with the newest exchange rates, it might happen that while rendering the search result page, the exchange rates are updated. Therefore, we need to make sure that exchange rates remain consistent in this scenario. We implement a transaction-safe converter that uses the same exchange rates data (available at the time of its creation) for its entire lifetime.

An example of transaction safe conversion, where the converter stores the same exchange rates data.

This way we make sure that the prices are converted using the same exchange rates data during the process. At the same time, we set a lifetime limit on the transaction safe converter, to assure that the converter object is used for time-limited jobs and prevent it from using outdated exchange rates.

Search results page where we compute multiple currency conversions during its rendering.

Search results page where we compute multiple currency conversions during its rendering.

What is next for the library?

The FX SDK Java library we built is supported by a reliable system based on Kafka, that provides exact currency conversion with short response times. As we are moving away from our monolith, the library will be used by different services, providing the foundation for all money-related processing.

Acknowledgment

This project was carried out together with our Senior Backend Engineer Constantin Șerban-Rădoi, who brought his valuable expertise to the project. I would like to also thank our Engineering Manager Daniel Huguenin, who provided great insights during the ideation phase, and everyone who gave valuable feedback during the entire project. Thank you all!

If you’re interested in joining our engineering team, check out our open roles.

--

--

GetYourGuide Tech Blog
GetYourGuide Tech Blog

GetYourGuide is the marketplace to book the best tours and activities globally. Meet our tech team here and see our open jobs on careers.getyourguide.com.