Internationalization
Internationalization (i18n) is the process of designing your Java application so it can be adapted to different languages, regions, and cultural conventions — without changing the source code. Whether you’re shipping to Tokyo, São Paulo, or Berlin, Java’s i18n support makes it possible to serve each audience correctly and naturally.
Note: “i18n” is shorthand for “internationalization” — there are 18 letters between the first “i” and the last “n”. Localization (l10n) is the follow-on step of actually providing the locale-specific content (translations, formatted numbers, etc.).
What Is Internationalization?
Internationalization means separating the parts of your app that change per locale (text, dates, numbers, currencies) from the parts that stay the same (business logic). You write the structure once; locale-specific content lives in external resource files or is handled by standard Java formatting APIs.
Key goals:
- Display text in the user’s language without recompiling
- Format dates, times, numbers, and currencies according to local conventions
- Handle character encoding and sorting rules correctly
- Support right-to-left scripts, plural forms, and locale-specific patterns
The Locale Class — Your Starting Point
Every i18n operation in Java revolves around java.util.Locale. A Locale represents a specific geographic, political, or cultural region.
import java.util.Locale;
public class LocaleDemo {
public static void main(String[] args) {
// Built-in constants
Locale usEnglish = Locale.US;
Locale france = Locale.FRANCE;
Locale japan = Locale.JAPAN;
// Build your own
Locale brazil = new Locale("pt", "BR"); // language + country
Locale german = Locale.forLanguageTag("de-DE"); // IETF BCP 47 tag (preferred)
System.out.println(usEnglish.getDisplayName()); // English (United States)
System.out.println(france.getDisplayName()); // French (France)
System.out.println(brazil.getDisplayName()); // Portuguese (Brazil)
}
}
Output:
English (United States)
French (France)
Portuguese (Brazil)
Tip: Prefer
Locale.forLanguageTag("...")orLocale.Builderfor new code — they follow the IETF BCP 47 standard and are more portable than the legacy two-argument constructor.
A Locale is composed of:
| Component | Example | Description |
|---|---|---|
| Language | en, de, ja | ISO 639 language code |
| Country/Region | US, DE, JP | ISO 3166 country code |
| Variant | WIN, POSIX | Rarely needed; platform-specific |
| Script | Latn, Arab | Writing system (BCP 47 only) |
The Default Locale
Java picks up the platform’s default locale at startup:
Locale defaultLocale = Locale.getDefault();
System.out.println(defaultLocale); // e.g., en_US
You can override it programmatically (useful in tests, not recommended for production):
Locale.setDefault(Locale.GERMANY);
Warning: Changing the default locale affects all locale-sensitive operations in the JVM — including third-party libraries. Always prefer passing a
Localeexplicitly to APIs rather than relying on the default.
Core i18n APIs at a Glance
Java’s internationalization support is spread across a few packages:
| Package | Key Classes | Purpose |
|---|---|---|
java.util | Locale, ResourceBundle, Currency | Locale identity, message bundles, currency info |
java.text | DateFormat, NumberFormat, MessageFormat, Collator | Formatting and sorting |
java.time | DateTimeFormatter, ZonedDateTime | Modern date/time formatting |
java.time.format | DateTimeFormatter, FormatStyle | Locale-aware date/time patterns |
For modern Java (11+), prefer the java.time API for dates and times. For message translation, ResourceBundle remains the standard.
A Minimal Internationalized “Hello” Example
Here is the simplest end-to-end i18n flow: externalize a greeting string into property files and load it at runtime based on the current locale.
Step 1 — Create resource files:
messages_en_US.properties
greeting=Hello, world!
messages_fr_FR.properties
greeting=Bonjour, le monde !
Step 2 — Load and use in Java:
import java.util.Locale;
import java.util.ResourceBundle;
public class HelloI18n {
public static void main(String[] args) {
Locale[] locales = { Locale.US, Locale.FRANCE };
for (Locale locale : locales) {
ResourceBundle bundle = ResourceBundle.getBundle("messages", locale);
System.out.println(locale.getDisplayName() + ": " + bundle.getString("greeting"));
}
}
}
Output:
English (United States): Hello, world!
French (France): Bonjour, le monde !
ResourceBundle.getBundle(baseName, locale) walks a well-defined lookup chain to find the best-matching file — more details in the ResourceBundle page.
Under the Hood
How Locale Resolution Works
When you call ResourceBundle.getBundle("messages", Locale.FRANCE), the JVM searches for property files in this priority order:
messages_fr_FR.propertiesmessages_fr.propertiesmessages_<default-locale>.propertiesmessages.properties(the fallback)
It stops at the first match it finds. This fallback chain lets you provide a generic base bundle while overriding only what differs per locale.
Character Encoding
Java String is always UTF-16 internally. Since Java 9, .properties files are assumed to be UTF-8 (previously ISO-8859-1 was the default, requiring Unicode escapes like é for é). Always save your property files as UTF-8 to avoid surprises.
Collation and Sorting
Alphabetical order is locale-dependent. Use java.text.Collator when sorting strings for display:
import java.text.Collator;
import java.util.*;
public class CollatorDemo {
public static void main(String[] args) {
List<String> words = Arrays.asList("Zebra", "äpfel", "Banane");
// Sort using German locale rules
Collator germanCollator = Collator.getInstance(Locale.GERMAN);
words.sort(germanCollator);
System.out.println(words);
}
}
Output:
[äpfel, Banane, Zebra]
A plain String.compareTo() would sort ä after all ASCII letters, which is wrong for a German user.
MessageFormat for Dynamic Messages
When a message contains dynamic values (like a username or count), use MessageFormat instead of string concatenation — it lets translators reorder placeholders naturally:
import java.text.MessageFormat;
import java.util.Locale;
public class MessageFormatDemo {
public static void main(String[] args) {
String pattern = "Dear {0}, you have {1} new messages.";
String result = MessageFormat.format(pattern, "Alice", 5);
System.out.println(result);
}
}
Output:
Dear Alice, you have 5 new messages.
In a real app, the pattern string itself comes from a ResourceBundle, so translators can rearrange {0} and {1} without touching Java code.
In This Section
- ResourceBundle — Load locale-specific strings, messages, and other objects from
.propertiesfiles or custom bundle classes, with caching and fallback resolution. - Formatting Dates & Times — Use
DateTimeFormatter,DateFormat, andFormatStyleto display dates and times in the format each locale expects. - Formatting Numbers & Currency — Use
NumberFormatandCurrencyto display integers, decimals, percentages, and monetary amounts according to locale conventions.
Related Topics
- Date/Time API (java.time) — Java 8’s modern date/time library is the foundation for locale-aware date and time formatting.
- ResourceBundle — The primary mechanism for externalizing translatable strings in Java applications.
- Enums — Enums pair well with i18n when mapping locale keys or supported languages to typed constants.
- Properties Class —
Propertiesunderpins.propertiesbundle files; understanding it helps you load and manage resource files manually. - String Methods — Methods like
toLowerCase(Locale)andtoUpperCase(Locale)have locale-sensitive overloads you should always use in i18n code.