Skip to content
Java i18n 5 min read

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:

TypeHow you define itBest for
PropertyResourceBundle.properties text fileSimple string translations (most common)
ListResourceBundleJava class extending ListResourceBundleNon-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:

  1. messages_fr_FR
  2. messages_fr
  3. messages (default fallback)

Tip: Always provide a default messages.properties so your app never crashes with a MissingResourceException on 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 native2ascii to convert UTF-8 properties to escaped form, or provide a custom ResourceBundle.Control.

Under the Hood

When you call ResourceBundle.getBundle(baseName, locale), Java performs these steps:

  1. Builds a candidate list of bundle names using the fallback chain described above.
  2. Searches the classpath (via the system ClassLoader) for a .class file first, then a .properties file, for each candidate.
  3. Caches the resultResourceBundle maintains an internal ConcurrentHashMap keyed 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.
  4. Inheritance between bundles — A PropertyResourceBundle for fr_FR delegates missing keys to the fr bundle, 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 foundgetString() throws MissingResourceException at runtime if the key is absent. Use containsKey(key) before calling getString() 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 mismatchnew 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 \uXXXX escapes, or use a UTF-8 aware PropertyResourceBundle constructor.
Last updated June 13, 2026
Was this helpful?