Skip to content
Java i18n 5 min read

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("...") or Locale.Builder for new code — they follow the IETF BCP 47 standard and are more portable than the legacy two-argument constructor.

A Locale is composed of:

ComponentExampleDescription
Languageen, de, jaISO 639 language code
Country/RegionUS, DE, JPISO 3166 country code
VariantWIN, POSIXRarely needed; platform-specific
ScriptLatn, ArabWriting 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 Locale explicitly to APIs rather than relying on the default.

Core i18n APIs at a Glance

Java’s internationalization support is spread across a few packages:

PackageKey ClassesPurpose
java.utilLocale, ResourceBundle, CurrencyLocale identity, message bundles, currency info
java.textDateFormat, NumberFormat, MessageFormat, CollatorFormatting and sorting
java.timeDateTimeFormatter, ZonedDateTimeModern date/time formatting
java.time.formatDateTimeFormatter, FormatStyleLocale-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:

  1. messages_fr_FR.properties
  2. messages_fr.properties
  3. messages_<default-locale>.properties
  4. messages.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 .properties files or custom bundle classes, with caching and fallback resolution.
  • Formatting Dates & Times — Use DateTimeFormatter, DateFormat, and FormatStyle to display dates and times in the format each locale expects.
  • Formatting Numbers & Currency — Use NumberFormat and Currency to display integers, decimals, percentages, and monetary amounts according to locale conventions.
  • 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 ClassProperties underpins .properties bundle files; understanding it helps you load and manage resource files manually.
  • String Methods — Methods like toLowerCase(Locale) and toUpperCase(Locale) have locale-sensitive overloads you should always use in i18n code.
Last updated June 13, 2026
Was this helpful?