Localization ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-16

Java ResourceBundle Class: Loading Localized Resources

You ask for English: ResourceBundle.getBundle("text", Locale.US). There is no text_en.properties, but there are text.properties and text_de.properties. On a server whose default locale is German, Java returns the German texts, not the base file. Why this happens and how to turn it off is shown in the section on the lookup order.

The ResourceBundle class (java.util.ResourceBundle) is a set of localized resources (strings and objects) that Java loads for the required locale by a base name. Together with the Locale class, a resource bundle in Java is the foundation of internationalization: UI texts live outside the code, and a new language is added with a new file, without recompiling.

The properties file format

A properties file (extension .properties) is a text file where each line defines a key = value pair, and lines starting with # or ! are comments. Every file of a bundle contains the same keys, translated for its own locale.

Keys are usually grouped with dots:

# Comment
group1.key1 = value1
group1.key2 = value2
group2.key1 = value3

For example, the English version:

label.button = Submit
label.field = Login
message.welcome = Welcome!

and the German version with the same keys:

label.button = Senden
label.field = Anmeldename
message.welcome = Willkommen!

Spaces around = are ignored, and : can be used instead of =. A long value continues on the next line if the line ends with a \ character.

File names and ResourceBundle.getBundle()

All files of one bundle share a base name, followed by a language code and a country code joined with underscores: text_fr_CA.properties. The static method ResourceBundle.getBundle(baseName, locale) loads the bundle, where baseName is the base name without suffixes and without the extension.

If the base name is text, a bundle can look like this:

text.properties          - base file (default values)
text_en_US.properties    - English, United States
text_de_DE.properties    - German, Germany
text_fr_CA.properties    - French, Canada

The following code loads the bundle with the base name text for French in Canada:

Locale locale = Locale.of("fr", "CA");
ResourceBundle rb = ResourceBundle.getBundle("text", locale);

Locale.of() was added in Java 19, and the new Locale("fr", "CA") constructor has been deprecated since then. On Java 8–18 use Locale.forLanguageTag("fr-CA") or new Locale("fr", "CA"). All the ways to create a locale are compared in the lesson Java Locale Class.

Files are looked up on the classpath, not in the working directory: in a Maven or Gradle project they go to src/main/resources. If the files are in a package, the base name is the fully qualified name with dots: the file com/example/i18n/text_fr.properties is loaded with getBundle("com.example.i18n.text", locale). Calling getBundle("text") without a locale uses Locale.getDefault().

The lookup order of getBundle()

ResourceBundle.getBundle() searches from the most specific name to the most general one: first the language and country of the requested locale, then the language only, then the same for the default locale, and only at the end the base file without suffixes. The first file found is used, and the more general files become its parents for missing keys.

Say the base name is text, the requested locale is fr_CA, and the JVM default locale is de_DE. The call ResourceBundle.getBundle("text", Locale.of("fr", "CA")) tries the candidates in this order:

  1. text_fr_CA
  2. text_fr
  3. text_de_DE — the default locale
  4. text_de
  5. text — the base file

That is why in the example at the start of the lesson Locale.US got text_de.properties: there are no text_en_US and text_en files, and Java checked the default locale de_DE before the base file. The one exception is a request for Locale.ROOT: it goes straight to the base file.

Locale.setDefault(Locale.of("de", "DE"));
// Files: text.properties, text_de.properties

ResourceBundle rb = ResourceBundle.getBundle("text", Locale.US);
System.out.println("[" + rb.getLocale() + "]");
// Output: [de]

ResourceBundle rb2 = ResourceBundle.getBundle("text", Locale.US,
        ResourceBundle.Control.getNoFallbackControl(ResourceBundle.Control.FORMAT_DEFAULT));
System.out.println("[" + rb2.getLocale() + "]");
// Output: [] - the base text.properties was loaded (Locale.ROOT)

Control.getNoFallbackControl() removes the default-locale step, so after text_en the search goes directly to the base file. This is useful on a server, where the JVM locale has nothing to do with the language of the user. How the default locale is set is covered in The default locale: getDefault and setDefault.

Easy to miss in modular apps

The getBundle() overloads that take a ResourceBundle.Control are not supported in named modules (Java 9+, a module-info.java is present) and throw UnsupportedOperationException. In a modular application use the java.util.spi.ResourceBundleProvider interface instead of Control.

Non-ASCII text in properties: Java 8 vs Java 9+

Since Java 9, PropertyResourceBundle reads properties files as UTF-8, so Japanese, Chinese, Cyrillic or Greek text can be written in the file as is. In Java 8 and earlier the files were read as ISO-8859-1, and every character outside that charset had to be written as a \uXXXX escape sequence, otherwise the user saw garbled text.

Java version Default encoding How to write «こんにちは» Note
Java 8 and earlier ISO-8859-1 \u3053\u3093\u306b\u3061\u306f Convert with the native2ascii tool or an IDE setting (in IntelliJ IDEA: Transparent native-to-ascii conversion)
Java 9+ UTF-8 (JEP 226) こんにちは If the file is not valid UTF-8, it is re-read as ISO-8859-1; the native2ascii tool was removed in JDK 9

\uXXXX escapes work in all versions, so old files don't need to be rewritten after moving to Java 9+. The old behavior can be restored with the system property -Djava.util.PropertyResourceBundle.encoding=ISO-8859-1.

Properties did not switch to UTF-8

The move to UTF-8 applies only to ResourceBundle. Properties.load(InputStream) still reads ISO-8859-1, even in Java 21, so the same UTF-8 file loaded through Properties comes out garbled. To read UTF-8, pass a Reader: props.load(new InputStreamReader(in, StandardCharsets.UTF_8)).

ResourceBundle example: strings for different locales

The program below loads the same bundle text for three locales and prints the values of the keys str1 and str2. The keys in the code stay the same; only the locale changes, and with it the file the text comes from.

Files in src/main/resources:

# text.properties
app.name = ExamClouds
str1 = Hello
str2 = Default bundle

# text_en_US.properties
str1 = Hello
str2 = USA

# text_de_DE.properties
str1 = Hallo
str2 = Deutschland
import java.util.Locale;
import java.util.ResourceBundle;

public class ResourceBundleExample1 {
    public static void main(String[] args) {
        printInfo(Locale.ROOT);
        printInfo(Locale.of("en", "US"));
        printInfo(Locale.of("de", "DE"));
    }

    private static void printInfo(Locale locale) {
        ResourceBundle rb = ResourceBundle.getBundle("text", locale);
        System.out.println(rb.getString("str1"));
        System.out.println(rb.getString("str2"));
        System.out.println();
    }
}

Output:

Hello
Default bundle

Hello
USA

Hallo
Deutschland

To go through all keys of a bundle, use keySet(). It returns the keys of the loaded file and of its parents, so for de_DE the list also contains app.name from the base text.properties:

import java.util.Locale;
import java.util.ResourceBundle;
import java.util.TreeSet;

public class ResourceBundleExample2 {
    public static void main(String[] args) {
        printInfo(Locale.ROOT);
        printInfo(Locale.of("de", "DE"));
    }

    private static void printInfo(Locale locale) {
        ResourceBundle rb = ResourceBundle.getBundle("text", locale);
        for (String key : new TreeSet<>(rb.keySet())) {
            System.out.println(key + " = " + rb.getString(key));
        }
        System.out.println();
    }
}

Output:

app.name = ExamClouds
str1 = Hello
str2 = Default bundle

app.name = ExamClouds
str1 = Hallo
str2 = Deutschland

Key order is not guaranteed

The order of keys in keySet() is undefined and does not match the order of lines in the file. If the order matters (a menu, a report, a test), sort the keys, for example with new TreeSet<>(rb.keySet()) as in the example above.

ResourceBundle methods

The main methods of ResourceBundle fall into three groups: loading a bundle (getBundle()), reading values by key (getString(), getObject(), getStringArray()) and iterating over keys (keySet(), getKeys(), containsKey()). A missing key causes an exception, not a null result.

Method What it does Edge case
static getBundle(String baseName, Locale locale) Loads the bundle for a locale (there are overloads without a locale, with a ClassLoader and with a Control) No file found at all — MissingResourceException
getString(String key) Returns the value for the key, from a parent bundle if needed No key — MissingResourceException; the value is not a string — ClassCastException
getObject(String key) Returns a value of any type (useful with ListResourceBundle) No key — MissingResourceException
getStringArray(String key) Returns an array of strings Not for properties files: a value there is always a String, so you get a ClassCastException
keySet() Returns a Set of all keys of the bundle and its parents The key order is undefined
getKeys() Returns an Enumeration of keys, the older form of keySet() Also includes the parent keys
containsKey(String key) Checks whether the key exists in the bundle or its parents Handy before getString() to avoid catching an exception
getLocale() Returns the locale of the file that was actually loaded For the base file — Locale.ROOT (an empty string)
static clearCache() Clears the cache of loaded bundles Needed if the files change while the JVM is running

MissingResourceException: when it is thrown

MissingResourceException is an unchecked exception (a subclass of RuntimeException) that ResourceBundle throws in two cases: getBundle() found no file of the bundle at all, including the base file, or getString() found the key neither in the file nor in its parents.

ResourceBundle rb = ResourceBundle.getBundle("text", Locale.ROOT);

System.out.println(rb.containsKey("str9"));
// Output: false

rb.getString("str9");
// java.util.MissingResourceException: Can't find resource for bundle
// java.util.PropertyResourceBundle, key str9

ResourceBundle.getBundle("labels", Locale.of("fr", "CA"));
// java.util.MissingResourceException: Can't find bundle for base name labels, locale fr_CA

To keep getBundle() from failing on an unknown locale, always ship a base file without suffixes (text.properties) next to the localized ones (text_en_US.properties, text_de_DE.properties), and put all keys into it. If the exception appears even though the file exists, the location is usually to blame: the file did not get onto the classpath, or the base name was given without the package.

ListResourceBundle vs PropertyResourceBundle

ResourceBundle is an abstract class with two standard implementations: PropertyResourceBundle reads strings from properties files, and ListResourceBundle takes resources from a Java class and can hold objects of any type. For each candidate name, getBundle() first looks for a class and then for a properties file.

Implementation Data source Value types When to use
PropertyResourceBundle File text_fr.properties String only UI texts: translators edit the files without a programmer
ListResourceBundle Class text_fr extends ListResourceBundle Any objects: numbers, arrays, icons Resources that can't be expressed as a string, or values computed in code
import java.util.ListResourceBundle;

public class Messages_fr extends ListResourceBundle {
    @Override
    protected Object[][] getContents() {
        return new Object[][] {
                {"greeting", "Bonjour"},
                {"maxUsers", 100}
        };
    }
}
ResourceBundle rb = ResourceBundle.getBundle("Messages", Locale.FRENCH);
System.out.println(rb.getString("greeting"));
int max = (Integer) rb.getObject("maxUsers");
System.out.println(max);
// Output:
// Bonjour
// 100

If a Messages_fr.properties file sits next to the Messages_fr class, the class is loaded and the file is ignored. The class name must match the candidate name exactly, including the package.

Frequently Asked Questions

Where do I put properties files so that ResourceBundle.getBundle() finds them?

On the classpath: in Maven and Gradle, in the src/main/resources folder. Pass the base name without the .properties extension, and if the file is in a package, with the package and dots: getBundle("com.example.i18n.text"). A file system path such as "C:/app/text" does not work. More details are in the section File names and ResourceBundle.getBundle().

What is the difference between ResourceBundle and Properties?

Properties is a mutable table from one file that you open and load yourself. ResourceBundle is an immutable set of files for different locales: it picks the matching file itself, takes missing keys from a parent, caches the result and, since Java 9, reads UTF-8. Application settings usually go into Properties, UI translations into ResourceBundle.

Why are changes to a properties file not visible until I restart the program?

getBundle() caches loaded bundles, and a repeated call returns the object from the cache. Call ResourceBundle.clearCache() to reset the cache, and the next getBundle() reads the files again. Fine-grained control over the cache lifetime is provided by Control.getTimeToLive() (outside named modules).

How do I insert values into a string from a ResourceBundle?

Write a template with numbered placeholders in the file, for example greeting = Hello, {0}! You have {1} new messages, and format it with java.text.MessageFormat: MessageFormat.format(rb.getString("greeting"), "Anna", 3) returns «Hello, Anna! You have 3 new messages». A single quote is an escape character in such a template, so an apostrophe is written as two quotes: it''s. A plain Don't miss {0} prints Dont miss {0}, without the apostrophe and without the value.

Comments

Please log in or register to have a possibility to add comment.