Sitemap
Alexander Obregon

Alexander Obregon

I post daily about programming topics and share what I learn as I go. For recaps, exclusive content, and to support me: https://alexanderobregon.substack.com

Building a Simple Currency Converter with Java

8 min readSep 30, 2025

--

Image Source

Currency conversion is a classic beginner project that teaches how input, arithmetic, and formatted output connect inside Java code. Behind the scenes, it relies on basic mechanics like reading user input, storing exchange rates, and applying multiplication or division to get the converted result. Paying attention to these mechanics also helps explain how the runtime moves data through variables and methods, while also making user interaction accurate and easy to follow.

I publish free articles like this daily, if you want to support my work and get access to exclusive content and weekly recaps, consider subscribing to my Substack.

Setting up the Conversion Process

Building a converter starts with the foundation that turns numbers into useful currency amounts. That foundation rests on how values are represented in memory, how input is collected from a user, how arithmetic applies to those numbers, and how the result is finally written in a form people recognize. Each of these steps connects to the runtime in its own way, and together they make the exchange process work smoothly.

Representing Exchange Rates in Java

Exchange rates are the multipliers that connect one currency to another. When a rate says one U.S. dollar equals 0.92 euros, that decimal value needs to be stored somewhere in memory. Java gives you different ways to hold numbers, but the choice affects accuracy and behavior.

A double is compact and fast, handled by the CPU directly. But when decimals are stored in binary form, they can lose precision. That’s why financial code usually avoids doubles. Instead, BigDecimal provides decimal arithmetic without the rounding drift you’d see in floating-point math.

import java.math.BigDecimal;

BigDecimal usdToEur = new BigDecimal("0.92");
BigDecimal amount = new BigDecimal("250");
BigDecimal result = amount.multiply(usdToEur);

System.out.println(result); // 230.00

That multiplies two exact decimal numbers. The constructor uses a string to avoid binary interpretation errors. This detail matters because if you had used new BigDecimal(0.92), the value inside wouldn’t be exactly 0.92 due to floating-point representation.

You can also pair BigDecimal with rounding control for display or storage.

import java.math.RoundingMode;

BigDecimal rounded = result.setScale(2, RoundingMode.HALF_UP);
System.out.println("Final stored value: " + rounded);

This enforces a scale of two decimal places, which matches how most currency systems present values.

Handling User Input

No converter is useful without some way for people to provide an amount. In Java, user input often flows through the Scanner class, which reads from standard input. In the background, Scanner wraps System.in and turns raw character data into numbers or strings.

import java.util.Scanner;
import java.math.BigDecimal;

Scanner scanner = new Scanner(System.in);
System.out.print("Enter a dollar amount: ");
BigDecimal usd = scanner.nextBigDecimal();

When the program pauses, it’s waiting on the console stream. After the user types and presses enter, Scanner parses the characters into a BigDecimal. If the input doesn’t represent a valid number, an exception is thrown, and the code must decide how to handle that situation.

Sometimes it helps to read a string first and then convert it manually, giving more control over errors or trimming spaces:

System.out.print("Type your amount: ");
String raw = scanner.nextLine().trim();
BigDecimal parsed = new BigDecimal(raw);

That pattern avoids situations where unexpected characters trip the scanner, but it shifts the parsing responsibility to the BigDecimal constructor. It’s a tradeoff between simplicity and control.

Performing Arithmetic for Conversion

Arithmetic is the step where exchange rates actually apply. Multiplication converts from one base unit into the target unit. Division works in reverse when you want to go back to the starting currency.

BigDecimal dollars = new BigDecimal("50");
BigDecimal usdToYen = new BigDecimal("147.25");

BigDecimal yen = dollars.multiply(usdToYen).setScale(2, RoundingMode.HALF_UP);
System.out.println("Converted to yen: " + yen);

The call to multiply produces a new object, as BigDecimal is immutable.

Division is slightly trickier, as currencies don’t always divide evenly. You need to specify a scale and rounding mode to prevent errors.

BigDecimal yenAmount = new BigDecimal("14725");
BigDecimal rate = new BigDecimal("147.25");

BigDecimal backToUsd = yenAmount.divide(rate, 2, RoundingMode.HALF_UP);
System.out.println("Converted back to USD: " + backToUsd);

This example avoids ArithmeticException by providing scale and rounding mode. Without them, Java demands an exact decimal result, which isn’t always possible with currency ratios.

Producing Formatted Output

Aftere a value is calculated, it has to be displayed in a form that people expect. Two decimal places, proper separators, and the right currency symbol all matter. Java provides formatting tools under the NumberFormat class.

import java.text.NumberFormat;
import java.util.Locale;
import java.math.BigDecimal;

BigDecimal euros = new BigDecimal("230.00");
NumberFormat eurFormat = NumberFormat.getCurrencyInstance(Locale.GERMANY);

System.out.println("Balance: " + eurFormat.format(euros));

The output uses the German locale, which places the euro symbol and decimal separators according to regional standards. The runtime looks up those patterns from locale data tables inside the JDK.

There are times when a custom format is useful. For instance, printing raw numbers without a currency symbol can make logs easier to parse.

NumberFormat plain = NumberFormat.getNumberInstance();
plain.setMinimumFractionDigits(2);
plain.setMaximumFractionDigits(2);

System.out.println("Stored value: " + plain.format(euros));

This creates consistent two-decimal output without attaching a symbol. Such flexibility keeps the display layer separate from the calculation layer.

Putting the Converter Together

After exploring the individual parts, the next step is combining them into a working piece of Java code. What makes this interesting is not just the surface logic, but how the runtime ties the steps into one continuous sequence. Input flows into variables, arithmetic turns those values into new results, and formatted output presents them back to the user. With that sequence established, you can start branching out into broader cases that handle multiple currencies and codes.

Building a Basic Program

A minimal converter contains everything in one place: a scanner for input, a BigDecimal for arithmetic, and formatting for the result. Bringing those together into a single class lets you see the path from start to finish.

import java.util.Scanner;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.Locale;

public class BasicConverter {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);

System.out.print("Type amount in USD: ");
BigDecimal usd = sc.nextBigDecimal();

BigDecimal usdToEur = new BigDecimal("0.92");
BigDecimal eur = usd.multiply(usdToEur).setScale(2, RoundingMode.HALF_UP);

NumberFormat fmt = NumberFormat.getCurrencyInstance(Locale.GERMANY);
System.out.println("Converted: " + fmt.format(eur));

// Don't close System.in-backed Scanner in simple console apps.
}
}

This example has the same skeleton as a larger converter, but with fewer moving parts. The runtime processes input, builds decimal objects, allocates new ones during arithmetic, and finally generates locale-aware text. Nothing leaves memory until the garbage collector cleans up the unused objects.

Get Alexander Obregon’s stories in your inbox

Join Medium for free to get updates from this writer.

You could also separate the rate into a method for better readability, showing that Java allows you to keep repeated constants in one place:

public static BigDecimal getUsdToEurRate() {
return new BigDecimal("0.92");
}

Calling this method instead of repeating the value prevents typing errors while still keeping the code small.

How the Runtime Manages the Steps

When this code runs, the flow is more mechanical than it looks at first glance. The scanner opens a buffer around the console stream. Once the user types a number and presses enter, that sequence of characters is parsed into a decimal. The runtime then allocates an object in memory to represent that decimal.

Multiplication through BigDecimal isn’t a primitive operation like multiplying two double values. Instead, it executes routines that handle decimal precision, creating a fresh object with the computed value. That immutability means the original number remains unchanged.

BigDecimal original = new BigDecimal("75");
BigDecimal rate = new BigDecimal("0.92");
BigDecimal product = original.multiply(rate);

System.out.println("Original: " + original);
System.out.println("New value: " + product);

Both numbers still exist in memory until the garbage collector decides one is no longer needed. That separation is part of what makes BigDecimal safe for currency math.

When output is formatted, NumberFormat looks up locale patterns inside the JDK’s resources. Those patterns define where to place commas, decimal points, and currency symbols. The resulting string is then written to standard output, which the console prints on the screen. Each of those steps involves string creation and memory allocation, though the developer doesn’t see it directly.

Expanding Conversion Options

A single rate can only get you so far. Real converters support multiple currencies, which means you need a data structure that maps codes to rates. A map fits this use case because it stores pairs of keys and values, letting you retrieve a rate quickly by code.

import java.util.HashMap;
import java.util.Map;
import java.math.BigDecimal;

Map<String, BigDecimal> rates = new HashMap<>();
rates.put("EUR", new BigDecimal("0.92"));
rates.put("JPY", new BigDecimal("147.10"));
rates.put("GBP", new BigDecimal("0.78"));

Lookups in a HashMap use hashing functions that turn a string key into a number, which is then mapped to a location in an internal array. This means the expected retrieval time is constant, even with many entries.

To apply one of these rates, you only need to request it from the map and perform multiplication:

BigDecimal usd = new BigDecimal("120");
BigDecimal rate = rates.get("JPY");

if (rate != null) {
BigDecimal yen = usd.multiply(rate).setScale(2, RoundingMode.HALF_UP);
System.out.println("Converted value: " + yen);
}

In this, the call to get either returns a decimal or null if the code doesn’t exist. That conditional guards against missing entries.

A second improvement is to loop through all supported codes and print out conversions for each one. This gives a broader picture without repeating the same logic multiple times.

for (String code : rates.keySet()) {
BigDecimal converted = usd.multiply(rates.get(code)).setScale(2, RoundingMode.HALF_UP);
System.out.println(code + ": " + converted);
}

The runtime iterates through the map, retrieving each rate by key. Each multiplication spawns a new object, and each string concatenation in println builds a temporary string object before output.

Handling Input for Currency Codes

Expanding rates only works if the user can pick the target. That means reading a currency code from input, turning it to a standard format, and checking against the map.

Scanner sc = new Scanner(System.in);
System.out.print("Target currency (EUR, JPY, GBP): ");
String code = sc.next().toUpperCase();

The call to toUpperCase keeps the format consistent, because the map stores its codes in uppercase. That way lookups work the same no matter how the user types them.

If the input matches a stored code, conversion proceeds. If not, you need a message that explains the problem without crashing.

BigDecimal usd = new BigDecimal("80");
BigDecimal rate = rates.get(code);

if (rate != null) {
BigDecimal converted = usd.multiply(rate).setScale(2, RoundingMode.HALF_UP);
System.out.println("Converted: " + converted + " " + code);
} else {
System.out.println("Currency code not recognized");
}

That conditional makes the flow safer by only performing arithmetic when a rate is available.

For better flexibility, you can also prompt for both source and target codes, then convert through a base currency like USD. This two-step process multiplies by one rate and divides by another.

System.out.print("Source currency (USD, EUR, GBP): ");
String source = sc.next().toUpperCase();
System.out.print("Target currency (USD, EUR, GBP): ");
String target = sc.next().toUpperCase();

BigDecimal sourceRate = rates.get(source);
BigDecimal targetRate = rates.get(target);

if (sourceRate != null && targetRate != null) {
BigDecimal amount = new BigDecimal("150");
BigDecimal usdBase = amount.divide(sourceRate, 6, RoundingMode.HALF_UP);
BigDecimal finalValue = usdBase.multiply(targetRate).setScale(2, RoundingMode.HALF_UP);
System.out.println("Result: " + finalValue + " " + target);
} else {
System.out.println("One of the codes was not recognized");
}

This method shows how you can chain two lookups to convert from one currency into another through a base. Each division and multiplication involves fresh decimal objects, while rounding makes sure the number stays precise at the desired scale.

Conclusion

Currency conversion in Java comes down to a sequence of mechanical steps handled by the runtime. Input is read from the console, exchange rates are stored and retrieved, arithmetic creates new decimal values with exact precision, and formatting turns those values into readable output. Each piece is small on its own, but together they show how Java moves data through memory, methods, and objects to produce a result that feels immediate to the user.

  1. BigDecimal Documentation
  2. Scanner Documentation
  3. NumberFormat Documentation
  4. HashMap Documentation

Thanks for reading! If you found this helpful, highlighting, clapping, or leaving a comment really helps me out.

--

--

Written by Alexander Obregon

I post daily about programming topics and share what I learn as I go. For recaps, exclusive content, and to support me: https://alexanderobregon.substack.com