ResourceBundle Class in Java

Naveen AnamaneniUpdated · 6 min read

A ResourceBundle holds locale-specific text, such as labels and messages, outside the program's logic. The program asks the bundle for a message by a key, like greeting, and the bundle returns the text in the right language for the current locale. There is one set of texts per language, all sharing a base name, and Java picks the best match automatically. Adding a language then means adding one file, with no change to the code. In this lesson, you will learn how bundles are named and found, how missing translations fall back to a default, and how to build sentences with values in them.

Properties Files

The usual form of a bundle is a set of .properties files on the class path, one per locale:

Messages.properties (the default)
greeting=Hello
farewell=Goodbye
result={0} scored {1} marks
Messages_de.properties (German)
greeting=Hallo
farewell=Auf Wiedersehen
result={0} hat {1} Punkte erreicht
File nameUsed for
Messages.propertiesThe default: used when nothing more specific matches
Messages_de.propertiesGerman, any country
Messages_de_AT.propertiesGerman as used in Austria
Messages_hi.propertiesHindi

Since Java 9, properties files are read as UTF-8, so any script can be typed directly.

Methods

MethodDescription
ResourceBundle.getBundle(baseName, locale)It is used to load the best bundle for a locale.
getString(key)It returns the text for a key.
containsKey(key)It tells whether a key exists in the bundle or its parents.
keySet()It returns all keys.
getLocale()It returns the locale of the bundle that was actually found.

How the Right Bundle Is Found

For getBundle("Messages", Locale.of("de", "AT")), Java looks for these, in order:

  1. Messages_de_AT
  2. Messages_de
  3. The same two steps for the JVM's default locale
  4. Messages (the default bundle)

The bundles it finds form a chain. When a key is missing in the most specific bundle, the lookup continues in the next, more general one. So a regional file needs only the texts that differ.

Example: Three Languages and a Fallback

A bundle can also be written as a Java class, which is what this example does so that it runs as a single file. The classes behave exactly like the properties files shown above.

Bundles.java▶ Try it
import java.io.StringReader;
import java.text.MessageFormat;
import java.util.ListResourceBundle;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
 
public class Bundles {
    // The default bundle: used when no better match exists.
    public static class Messages extends ListResourceBundle {
        @Override
        protected Object[][] getContents() {
            return new Object[][] {
                {"greeting", "Hello"},
                {"farewell", "Goodbye"},
                {"result", "{0} scored {1} marks"},
            };
        }
    }
 
    public static class Messages_de extends ListResourceBundle {
        @Override
        protected Object[][] getContents() {
            return new Object[][] {
                {"greeting", "Hallo"},
                {"farewell", "Auf Wiedersehen"},
                {"result", "{0} hat {1} Punkte erreicht"},
            };
        }
    }
 
    // Hindi translates only some keys; the rest fall back to the default.
    public static class Messages_hi extends ListResourceBundle {
        @Override
        protected Object[][] getContents() {
            return new Object[][] {
                {"greeting", "नमस्ते"},
                {"result", "{0} को {1} अंक मिले"},
            };
        }
    }
 
    static void show(Locale locale) {
        ResourceBundle bundle =
            ResourceBundle.getBundle("Bundles$Messages", locale);
        String result = MessageFormat.format(
            bundle.getString("result"), "Navika", 91);
        System.out.println(locale.toLanguageTag() + ": "
            + bundle.getString("greeting") + " | "
            + bundle.getString("farewell") + " | " + result);
    }
 
    public static void main(String[] args) throws Exception {
        Locale.setDefault(Locale.ENGLISH);
        show(Locale.ENGLISH);
        show(Locale.GERMANY);
        show(Locale.of("hi", "IN"));
        show(Locale.JAPAN);
 
        try {
            ResourceBundle.getBundle("Bundles$Messages", Locale.GERMANY)
                .getString("title");
        } catch (MissingResourceException e) {
            System.out.println("Missing key: " + e.getKey());
        }
 
        // The same idea with the text of a .properties file.
        String file = "greeting=Bonjour\nfarewell=Au revoir\n";
        ResourceBundle french =
            new PropertyResourceBundle(new StringReader(file));
        System.out.println("fr: " + french.getString("greeting")
            + " | " + french.getString("farewell"));
    }
}
Output
en: Hello | Goodbye | Navika scored 91 marks
de-DE: Hallo | Auf Wiedersehen | Navika hat 91 Punkte erreicht
hi-IN: नमस्ते | Goodbye | Navika को 91 अंक मिले
ja-JP: Hello | Goodbye | Navika scored 91 marks
Missing key: title
fr: Bonjour | Au revoir

Explanation:

  1. English has no bundle of its own, so the default Messages is used.
  2. German finds Messages_de; all three texts are German.
  3. Hindi has a bundle with only two keys. greeting and result come from it; farewell is missing there, so it falls back to the default and appears in English.
  4. Japanese has no bundle at all, so everything comes from the default.
  5. A key that exists nowhere throws MissingResourceException.
  6. The last lines read the text of a properties file with PropertyResourceBundle, showing that the file format gives the same result.

Sentences with Values: MessageFormat

The result text contains placeholders, {0} and {1}. MessageFormat.format(pattern, values...) fills them in. This matters because word order differs between languages: English says "Navika scored 91 marks", while the German and Hindi sentences put the number in a different place. With placeholders, the translator decides where each value goes.

Don'tDo
name + " " + bundle.getString("scored") + " " + marksMessageFormat.format(bundle.getString("result"), name, marks)

Placeholders can also format their value: {1,number} uses the locale's number format, and {2,date,long} a long date.

Common Mistakes

No default bundle. A user with an unsupported language gets MissingResourceException instead of a fallback.

Building sentences from pieces. The word order breaks in other languages.

A wrong base name. For a file in a package, the base name includes the package: com.school.Messages.

An apostrophe in a MessageFormat pattern. A single ' starts a quoted section; write it twice, '', to show one.

Interview Corner

What is a ResourceBundle?

A ResourceBundle contains locale-specific objects, usually strings, identified by keys. Bundles are grouped into a family sharing a base name, with one member per locale named Base_language_COUNTRY, commonly as .properties files. ResourceBundle.getBundle(baseName, locale) finds the most specific match and links it to more general bundles down to the default, so missing keys fall back to parent bundles. Text with variable parts uses placeholders filled in with MessageFormat. This keeps translations out of the code so languages can be added without recompiling.

How does Java search for a resource bundle?

Starting with the requested locale, it looks for base_lang_COUNTRY, then base_lang. If none is found, it repeats that for the default locale, and finally uses the base bundle. Found bundles are chained as parents, so getString searches from the most specific to the base. If no bundle or key is found, MissingResourceException is thrown.

Practice

  1. Easy: Add a French bundle with all three keys and show it with Locale.FRANCE.
  2. Harder: Create real Messages.properties and Messages_de.properties files, put them next to your class, load them with getBundle("Messages", locale), and compare the results with this example.

Quick check: ResourceBundle

4 questions
1Which file is the default bundle for base name Messages?
2A key is missing in Messages_hi but present in Messages. What does getString return?
3What fills {0} and {1} in a message?
4A key exists in no bundle. What happens?

Key Takeaways

Was this tutorial helpful?

Lesson 504 of 578