Internationalization with Currency in Java
Money has the most rules of all. The symbol differs ($, €, ₹, ¥), its position differs (before the number in the US, after it in Germany), the separators differ, and so does the number of decimals: two for dollars, none for Japanese yen. Java handles all of this with NumberFormat.getCurrencyInstance(locale) for formatting and the Currency class for information about a currency. In this lesson, you will learn how to format amounts, how to show one currency in another region's style, why amounts should be BigDecimal, and finish with this chapter's checkpoint: an invoice printed in English (US), German and Hindi (India).
Methods
| Method | Description |
|---|---|
NumberFormat.getCurrencyInstance(locale) | It is used to format an amount as money for a locale. |
numberFormat.setCurrency(currency) | It makes the formatter show a different currency. |
Currency.getInstance(locale) | It returns the currency used in a country. |
Currency.getInstance("EUR") | It returns a currency by its three-letter ISO 4217 code. |
getCurrencyCode() | It returns the code, such as USD. |
getSymbol(locale) | It returns the symbol as shown in that locale. |
getDisplayName(locale) | It returns the name, such as "Indian Rupee". |
getDefaultFractionDigits() | It returns the usual number of decimals. |
Example: One Amount, Seven Locales
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.Currency;
import java.util.Locale;
public class Money {
public static void main(String[] args) {
BigDecimal amount = new BigDecimal("1234567.5");
Locale[] locales = {Locale.US, Locale.UK, Locale.GERMANY,
Locale.FRANCE, Locale.of("en", "IN"), Locale.of("hi", "IN"),
Locale.JAPAN};
for (Locale locale : locales) {
NumberFormat money = NumberFormat.getCurrencyInstance(locale);
Currency currency = Currency.getInstance(locale);
System.out.println(locale.toLanguageTag() + " "
+ money.format(amount) + " | "
+ currency.getCurrencyCode() + ", "
+ currency.getDefaultFractionDigits() + " decimals");
}
// A currency is not tied to the reader's locale:
// show a price in euros to a reader in the US.
NumberFormat usStyle = NumberFormat.getCurrencyInstance(Locale.US);
usStyle.setCurrency(Currency.getInstance("EUR"));
System.out.println("Euros, US style: " + usStyle.format(amount));
Currency inr = Currency.getInstance("INR");
System.out.println(inr.getDisplayName(Locale.ENGLISH) + " | "
+ inr.getSymbol(Locale.of("en", "IN")) + " | "
+ inr.getSymbol(Locale.US));
NumberFormat negative = NumberFormat.getCurrencyInstance(Locale.US);
System.out.println(negative.format(new BigDecimal("-49.99")));
}
}en-US $1,234,567.50 | USD, 2 decimals
en-GB £1,234,567.50 | GBP, 2 decimals
de-DE 1.234.567,50 € | EUR, 2 decimals
fr-FR 1 234 567,50 € | EUR, 2 decimals
en-IN ₹1,234,567.50 | INR, 2 decimals
hi-IN ₹1,234,567.50 | INR, 2 decimals
ja-JP ¥1,234,568 | JPY, 0 decimals
Euros, US style: €1,234,567.50
Indian Rupee | ₹ | ₹
-$49.99Explanation:
- Symbol and position:
$and£come first; the euro sign comes last in Germany and France, after a no-break space. - Decimals: yen has no smaller unit, so the amount is rounded to a whole number.
getDefaultFractionDigits()reports 0 for JPY and 2 for the others. - Formatting is not conversion. Every line shows the same number, 1,234,567.50, with a different currency label. Java doesn't convert amounts; exchange rates must come from somewhere else.
- Locale and currency are separate.
setCurrency(EUR)on a US formatter prints a euro price in US style:€1,234,567.50. Use this when a shop charges in euros but the reader is American. - Negative amounts follow the locale too:
-$49.99in the US.
Use BigDecimal for Money
double can't represent most decimal fractions exactly, so sums drift: 0.1 + 0.2 is 0.30000000000000004. For money, use BigDecimal, created from a string (new BigDecimal("1499.50")), and round explicitly with setScale(2, RoundingMode.HALF_UP). NumberFormat.format accepts a BigDecimal directly.
Checkpoint: An Invoice in Three Locales
This program combines the whole chapter: a resource bundle for the labels, a date formatter, a percent formatter and a currency formatter, all driven by one Locale.
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Currency;
import java.util.List;
import java.util.ListResourceBundle;
import java.util.Locale;
import java.util.ResourceBundle;
public class Invoice {
record Line(String item, int quantity, BigDecimal price) {
BigDecimal total() {
return price.multiply(BigDecimal.valueOf(quantity));
}
}
public static class Labels extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{"invoice", "Invoice"}, {"date", "Date"},
{"subtotal", "Subtotal"}, {"tax", "Tax"},
{"total", "Total"},
};
}
}
public static class Labels_de extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{"invoice", "Rechnung"}, {"date", "Datum"},
{"subtotal", "Zwischensumme"}, {"tax", "Steuer"},
{"total", "Gesamt"},
};
}
}
public static class Labels_hi extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{"invoice", "चालान"}, {"date", "दिनांक"},
{"subtotal", "उप-योग"}, {"tax", "कर"},
{"total", "कुल"},
};
}
}
static void print(Locale locale, List<Line> lines, LocalDate date,
BigDecimal taxRate) {
ResourceBundle labels =
ResourceBundle.getBundle("Invoice$Labels", locale);
NumberFormat money = NumberFormat.getCurrencyInstance(locale);
NumberFormat percent = NumberFormat.getPercentInstance(locale);
DateTimeFormatter dates = DateTimeFormatter
.ofLocalizedDate(FormatStyle.LONG).withLocale(locale);
BigDecimal subtotal = lines.stream().map(Line::total)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal tax = subtotal.multiply(taxRate)
.setScale(2, RoundingMode.HALF_UP);
System.out.println("=== " + labels.getString("invoice") + " ("
+ locale.toLanguageTag() + ", "
+ Currency.getInstance(locale).getCurrencyCode() + ") ===");
System.out.println(labels.getString("date") + ": "
+ date.format(dates));
for (Line line : lines) {
System.out.println(" " + line.quantity() + " x " + line.item()
+ " " + money.format(line.total()));
}
System.out.println(labels.getString("subtotal") + ": "
+ money.format(subtotal));
System.out.println(labels.getString("tax") + " ("
+ percent.format(taxRate) + "): " + money.format(tax));
System.out.println(labels.getString("total") + ": "
+ money.format(subtotal.add(tax)));
System.out.println();
}
public static void main(String[] args) {
Locale.setDefault(Locale.ENGLISH);
List<Line> lines = List.of(
new Line("Java course", 2, new BigDecimal("1499.50")),
new Line("Notebook", 12, new BigDecimal("85.25")));
LocalDate date = LocalDate.of(2026, 11, 13);
BigDecimal taxRate = new BigDecimal("0.18");
print(Locale.US, lines, date, taxRate);
print(Locale.GERMANY, lines, date, taxRate);
print(Locale.of("hi", "IN"), lines, date, taxRate);
}
}=== Invoice (en-US, USD) ===
Date: November 13, 2026
2 x Java course $2,999.00
12 x Notebook $1,023.00
Subtotal: $4,022.00
Tax (18%): $723.96
Total: $4,745.96
=== Rechnung (de-DE, EUR) ===
Datum: 13. November 2026
2 x Java course 2.999,00 €
12 x Notebook 1.023,00 €
Zwischensumme: 4.022,00 €
Steuer (18 %): 723,96 €
Gesamt: 4.745,96 €
=== चालान (hi-IN, INR) ===
दिनांक: 13 नवंबर 2026
2 x Java course ₹2,999.00
12 x Notebook ₹1,023.00
उप-योग: ₹4,022.00
कर (18%): ₹723.96
कुल: ₹4,745.96Explanation:
printreceives aLocaleand builds everything from it: the labels bundle, the money formatter, the percent formatter and the date formatter. Nothing in the method names a language or a symbol.- Labels come from the
Labelsbundles: "Invoice", "Rechnung" and the Hindi word. - The date is written in each locale's long style.
- The tax rate
0.18prints as18%or18 %. - Amounts are
BigDecimalvalues; the tax is rounded to two decimals withHALF_UP. - Adding a fourth language would need one more
Labels_xxclass (or properties file) and one more call toprint.
The three invoices show the same numbers with different currency symbols because the example only formats. A real invoice has one currency, fixed when the sale is made; you would format that currency in the reader's style with setCurrency.
Common Mistakes
Using double for money. Rounding errors accumulate; use BigDecimal.
Thinking a currency formatter converts. It only labels the number.
Taking the currency from the reader's locale. A German reader may be looking at a price in dollars.
Hard-coding two decimals. Yen has none; some currencies have three.
Putting the symbol in by string joining. The position and spacing vary by locale.
Interview Corner
How do you format currency for different locales in Java?
Use NumberFormat.getCurrencyInstance(locale).format(amount). The locale determines the symbol's position, the grouping and decimal separators and the number of fraction digits. The currency defaults to that of the locale's country and can be changed with setCurrency(Currency.getInstance("EUR")). java.util.Currency provides the ISO 4217 code, symbol, display name and default fraction digits. Amounts should be BigDecimal, and formatting does not perform currency conversion.
Practice
- Easy: Print 2,500.75 as money for
Locale.CANADA,Locale.CANADA_FRENCHandLocale.ITALY. - Harder: Change the invoice so the sale is always in US dollars, and each locale shows that dollar amount in its own style using
setCurrency.
Key Takeaways
Was this tutorial helpful?