12.1 Locales and Resource Bundles

Key Takeaways

  • A Locale pairs a lowercase ISO 639 language code with an optional uppercase ISO 3166 country code; Java 19 deprecated the Locale constructors in favour of Locale.of, Locale.forLanguageTag, and Locale.Builder.
  • Locale.getDefault(Category.DISPLAY) and Locale.getDefault(Category.FORMAT) are independent, letting an application show its interface in one language while formatting numbers and dates for another region.
  • getBundle tries the requested locale from most specific to least specific, then repeats that narrowing for the default locale, and finally the base name alone before throwing MissingResourceException.
  • Once a bundle is selected, missing keys are resolved through that bundle's parent chain down to the base bundle only; default-locale bundles that were merely candidates are never parents.
  • getString throws the unchecked MissingResourceException when a key is absent anywhere in the chain and ClassCastException when a ListResourceBundle stores a non-String value.
Last updated: September 2026

Locales and Resource Bundles

Implementing Localization is one of the ten top-level topics on the 1Z0-830 exam, and it is the topic candidates most often skip. The whole objective reads: "Implement localization using locales and resource bundles. Parse and format messages, dates, times, and numbers, including currency and percentage values." This section covers the first sentence; section 12.2 covers the second.

Localization in Java rests on two types. A Locale identifies who the user is - a language, and optionally a region. A ResourceBundle supplies what to show them - the translated text keyed by a stable identifier. Neither one translates anything for you; they select and load the right values.


1. Constructing a Locale

A locale is composed of a language code (lowercase, ISO 639, e.g. fr), an optional country/region code (uppercase, ISO 3166, e.g. CA), and an optional variant. The exam expects you to spot a swapped case pattern instantly: Locale.of("FR", "ca") is legal Java but semantically wrong.

Java 19 deprecated the Locale constructors, so Java 21 code should use one of three modern routes:

// 1. Static factory (the direct replacement for the deprecated constructors)
Locale enUS  = Locale.of("en", "US");
Locale french = Locale.of("fr");

// 2. BCP 47 language tag - note the HYPHEN, not an underscore
Locale frCA = Locale.forLanguageTag("fr-CA");
System.out.println(frCA.toLanguageTag());   // fr-CA

// 3. Builder, for full BCP 47 control
Locale deDE = new Locale.Builder()
        .setLanguage("de")
        .setRegion("DE")
        .build();

Predefined constants come in two flavours that are easy to confuse:

ConstantLanguageCountrytoString()
Locale.GERMANde(none)de
Locale.GERMANYdeDEde_DE
Locale.FRENCHfr(none)fr
Locale.CANADA_FRENCHfrCAfr_CA

Note that toString() uses underscores while toLanguageTag() uses hyphens - the same distinction that separates a resource bundle file name from a language tag.

Default Locales Come in Two Categories

Locale.getDefault();                          // convenience: the FORMAT default
Locale.getDefault(Locale.Category.DISPLAY);   // menus, labels, display names
Locale.getDefault(Locale.Category.FORMAT);    // numbers, currency, dates
Locale.setDefault(Locale.of("es", "MX"));     // sets BOTH categories

Splitting the two lets an application show its user interface in one language while formatting numbers and dates according to a different regional convention.

getDisplayLanguage() and getDisplayCountry() are themselves localizable, which is a favourite exam nuance:

Locale target = Locale.of("de", "DE");
System.out.println(target.getDisplayLanguage(Locale.US));      // German
System.out.println(target.getDisplayLanguage(Locale.FRANCE));  // allemand
System.out.println(target.getLanguage());                      // de  (never localized)

2. The Two Kinds of ResourceBundle

ResourceBundle is an abstract class with two concrete forms in the standard library.

PropertyResourceBundle is created automatically from a .properties file. You never name the class:

# Messages_fr.properties
greeting = Bonjour
farewell = Au revoir

Since Java 9 these files are read as UTF-8 by default (with a fallback to ISO-8859-1 for legacy files), so accented characters no longer need \\uXXXX escapes.

ListResourceBundle is a Java class, which lets values be any object rather than only strings:

public class Messages_ja extends ListResourceBundle {
    @Override
    protected Object[][] getContents() {
        return new Object[][] {
            { "greeting", "こんにちは" },
            { "maxItems", 25 }                       // an Integer, not a String
        };
    }
}

Loading and reading them is identical either way:

ResourceBundle rb = ResourceBundle.getBundle("Messages", Locale.of("fr", "CA"));
String greeting = rb.getString("greeting");
  • getString(key) throws MissingResourceException (unchecked) when the key is nowhere in the chain, and ClassCastException when the stored value is not a String.
  • Use getObject(key) for non-string values and getStringArray(key) for String[].
  • keySet() returns every key visible through the bundle and its parents; containsKey(key) is the safe pre-check.

3. Bundle Selection: The Highest-Yield Rule in This Topic

getBundle(baseName, requestedLocale) builds a list of candidate names from most specific to least specific, and takes the first one that exists. At each candidate name it looks for a class first, then a .properties file.

For base name Messages, requested locale fr_CA, and default locale en_US, the search order is:

1. Messages_fr_CA        (requested: language + country)
2. Messages_fr           (requested: language only)
3. Messages_en_US        (default locale: language + country)
4. Messages_en           (default locale: language only)
5. Messages              (base bundle, no suffix)
   -> otherwise MissingResourceException

Key Lookup Uses the Parent Chain, Not the Search List

Once a bundle is chosen, missing keys are resolved by walking that bundle's parent chain, which strips one locale component at a time down to the base bundle. The default-locale bundles are not in the chain unless the match itself came from the default-locale pass.

Given these files:

FileKeys
Messages_fr.propertiesgreeting
Messages_en.propertiesgreeting, hint
Messages.propertiesgreeting, farewell

and a request for fr_CA with default en_US:

ResourceBundle rb = ResourceBundle.getBundle("Messages", Locale.of("fr", "CA"));
rb.getString("greeting");   // from Messages_fr        - the selected bundle
rb.getString("farewell");   // from Messages           - via the parent chain
rb.getString("hint");       // MissingResourceException - Messages_en is NOT a parent

That last line is the single most commonly tested trap in the localization objective. The en bundle was a candidate during selection, but selection stopped at Messages_fr; the chain is Messages_fr then Messages, full stop.


4. Practical Rules and Module Considerations

  • Bundle files live on the classpath (or module path) at the package matching the base name: getBundle("com.app.Messages", ...) resolves com/app/Messages_fr.properties.
  • In a named module, the module that reads the bundle needs access to those resources; either keep the bundles in the same module, or opens the package holding them.
  • Keys must be identical across every translation. A key present only in Messages_fr and absent from Messages will fail for every other locale.
  • Bundles are cached, so repeated getBundle calls with the same base name, locale, and loader return the same instance.
  • Always provide a base bundle with no locale suffix. Without it, any request whose language matches nothing at all fails with MissingResourceException rather than degrading gracefully.
Loading diagram...
Resource Bundle Selection Versus Parent-Chain Key Lookup
Test Your Knowledge

An application calls ResourceBundle.getBundle("Labels", Locale.of("fr", "CA")) while the JVM default locale is en_US. In what order does getBundle look for candidate bundles?

A
B
C
D
Test Your Knowledge

Only three files exist: Messages_fr.properties (key greeting), Messages_en.properties (keys greeting and hint), and Messages.properties (keys greeting and farewell). With the default locale set to en_US, what happens?

ResourceBundle rb = ResourceBundle.getBundle("Messages", Locale.of("fr", "CA"));
System.out.println(rb.getString("farewell"));
System.out.println(rb.getString("hint"));

A
B
C
D
Test Your Knowledge

Which statement about obtaining a Locale in Java SE 21 is correct?

A
B
C
D
Test Your Knowledge

A ListResourceBundle stores the entry { "maxItems", 25 } where 25 is an Integer. What happens when client code calls bundle.getString("maxItems")?

A
B
C
D