ResourceBundle
When you want your Java application to display different text, labels, or messages depending on the user’s language or country, ResourceBundle is the standard way to do it. Instead of hard-coding strings inside your code, you put them in external files and let Java load the right one automatically based on the active Locale.
What Is a ResourceBundle?
A ResourceBundle is a container for locale-specific objects — usually String key-value pairs. You write one bundle per language (or language + country combination), and at runtime Java picks the best match for the user’s Locale. Your application code stays the same regardless of the target language.
There are two main flavour of bundles:
| Type | How you define it | Best for |
|---|---|---|
PropertyResourceBundle | .properties text file | Simple string translations (most common) |
ListResourceBundle | Java class extending ListResourceBundle | Non-string objects (icons, colours, etc.) |
Using Properties Files
The most common approach is to create .properties files named with a base name, followed by an optional locale suffix.
Step 1 — Create the Properties Files
Place these files on the classpath (e.g., src/main/resources/):
messages.properties (default / fallback)
greeting=Hello!
farewell=Goodbye!
messages_fr.properties (French)
greeting=Bonjour !
farewell=Au revoir !
messages_de.properties (German)
greeting=Hallo!
farewell=Auf Wiedersehen!
Note: The default bundle (
messages.properties) is always the fallback. If Java cannot find a match for the requested locale, it falls back to this file.
Step 2 — Load the Bundle in Java
import java.util.Locale;
import java.util.ResourceBundle;
public class Greeting {
public static void main(String[] args) {
Locale french = new Locale("fr");
ResourceBundle bundle = ResourceBundle.getBundle("messages", french);
System.out.println(bundle.getString("greeting"));
System.out.println(bundle.getString("farewell"));
}
}
Output:
Bonjour !
Au revoir !
Switch french for new Locale("de") and you instantly get German. Switch to a locale with no matching file (say, Spanish) and Java gracefully falls back to messages.properties.
The Lookup (Fallback) Chain
Java follows a well-defined search order when resolving a bundle. For ResourceBundle.getBundle("messages", new Locale("fr", "FR")), it tries these names in order until one exists:
messages_fr_FRmessages_frmessages(default fallback)
Tip: Always provide a default
messages.propertiesso your app never crashes with aMissingResourceExceptionon an unexpected locale.
Iterating All Keys
import java.util.Locale;
import java.util.ResourceBundle;
public class BundleKeys {
public static void main(String[] args) {
ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.FRENCH);
for (String key : bundle.keySet()) {
System.out.println(key + " = " + bundle.getString(key));
}
}
}
Output:
farewell = Au revoir !
greeting = Bonjour !
Using ListResourceBundle
When your translations include non-string objects (images, arrays, or other objects), subclass ListResourceBundle directly.
import java.util.ListResourceBundle;
// File: AppResources_en.java
public class AppResources_en extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{ "app.title", "My Application" },
{ "max.retries", 3 }, // Integer, not String
{ "supported.langs", new String[]{"en", "fr", "de"} }
};
}
}
import java.util.Locale;
import java.util.ResourceBundle;
public class ListBundleDemo {
public static void main(String[] args) {
ResourceBundle bundle = ResourceBundle.getBundle("AppResources", Locale.ENGLISH);
System.out.println(bundle.getString("app.title"));
System.out.println(bundle.getObject("max.retries"));
String[] langs = (String[]) bundle.getObject("supported.langs");
for (String lang : langs) {
System.out.print(lang + " ");
}
}
}
Output:
My Application
3
en fr de
Parameterized Messages with MessageFormat
Real applications often need to insert dynamic values into translated strings — for example, “Welcome, Alice!”. The standard way is to use placeholders and MessageFormat.
messages.properties
welcome=Welcome, {0}! You have {1} new messages.
import java.text.MessageFormat;
import java.util.Locale;
import java.util.ResourceBundle;
public class WelcomeMessage {
public static void main(String[] args) {
ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.ENGLISH);
String pattern = bundle.getString("welcome");
String result = MessageFormat.format(pattern, "Alice", 5);
System.out.println(result);
}
}
Output:
Welcome, Alice! You have 5 new messages.
Each .properties file for other languages would translate the full sentence while keeping {0} and {1} in place.
Encoding and UTF-8 (Java 9+)
Before Java 9, .properties files were read as ISO-8859-1, meaning non-Latin characters had to be written as Unicode escapes (A). Since Java 9, they are read as UTF-8 by default, so you can write accented characters and CJK scripts directly.
# messages_ja.properties (Java 9+, saved as UTF-8)
greeting=こんにちは!
Warning: If you’re on Java 8 or earlier, use
native2asciito convert UTF-8 properties to escaped form, or provide a customResourceBundle.Control.
Under the Hood
When you call ResourceBundle.getBundle(baseName, locale), Java performs these steps:
- Builds a candidate list of bundle names using the fallback chain described above.
- Searches the classpath (via the system
ClassLoader) for a.classfile first, then a.propertiesfile, for each candidate. - Caches the result —
ResourceBundlemaintains an internalConcurrentHashMapkeyed by(baseName, locale, classLoader). A second call for the same combination returns the cached instance immediately without re-reading the file system. This cache is soft-referenced so the JVM can reclaim it under memory pressure. - Inheritance between bundles — A
PropertyResourceBundleforfr_FRdelegates missing keys to thefrbundle, which in turn delegates to the default bundle.getString()walks this chain transparently.
The cache means that in a long-running server it is safe to call getBundle on every request without performance concerns. If you ever need to force a reload (e.g., after hot-deploying new translation files), call ResourceBundle.clearCache().
Common Pitfalls
- Key not found —
getString()throwsMissingResourceExceptionat runtime if the key is absent. UsecontainsKey(key)before callinggetString()when in doubt. - Wrong classpath — Properties files must be on the classpath root (or a sub-package matching the base-name prefix). A common mistake is placing them under
src/but forgetting to include them in the build output. - Locale mismatch —
new Locale("FR")(uppercase) is silently normalised to lowercase, but double-check your bundle file names match Java’s expected conventions (language in lowercase, country in uppercase:messages_fr_FR.properties). - Encoding on Java 8 — Write non-ASCII characters as
\uXXXXescapes, or use a UTF-8 awarePropertyResourceBundleconstructor.
Related Topics
- Internationalization — the broad i18n overview and how
Localefits into Java’s design - Formatting Dates & Times — using
DateTimeFormatterandDateFormatwith aLocale - Formatting Numbers & Currency —
NumberFormatandCurrencyfor locale-aware numeric output - Properties Class — the lower-level
java.util.Propertiesused internally byPropertyResourceBundle - String Methods — manipulating the strings you retrieve from a bundle