ResourceBundle Class in Java
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:
greeting=Hello
farewell=Goodbye
result={0} scored {1} marksgreeting=Hallo
farewell=Auf Wiedersehen
result={0} hat {1} Punkte erreicht| File name | Used for |
|---|---|
Messages.properties | The default: used when nothing more specific matches |
Messages_de.properties | German, any country |
Messages_de_AT.properties | German as used in Austria |
Messages_hi.properties | Hindi |
Since Java 9, properties files are read as UTF-8, so any script can be typed directly.
Methods
| Method | Description |
|---|---|
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:
Messages_de_ATMessages_de- The same two steps for the JVM's default locale
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.
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"));
}
}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 revoirExplanation:
- English has no bundle of its own, so the default
Messagesis used. - German finds
Messages_de; all three texts are German. - Hindi has a bundle with only two keys.
greetingandresultcome from it;farewellis missing there, so it falls back to the default and appears in English. - Japanese has no bundle at all, so everything comes from the default.
- A key that exists nowhere throws
MissingResourceException. - 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't | Do |
|---|---|
name + " " + bundle.getString("scored") + " " + marks | MessageFormat.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
- Easy: Add a French bundle with all three keys and show it with
Locale.FRANCE. - Harder: Create real
Messages.propertiesandMessages_de.propertiesfiles, put them next to your class, load them withgetBundle("Messages", locale), and compare the results with this example.
Key Takeaways
Was this tutorial helpful?