A ResourceBundle maps stable message keys to locale-specific values and can obtain missing values from a parent bundle.
Java ResourceBundle: locale fallback and missing message keys
This program targets Java 8 without preview flags. Use a full JDK for compiler-based examples.
Keep identifiers separate from labels
A receipt screen stores a status identifier but displays a translated label. Changing the label must not change its stored key or business comparison. The base bundle supplies common keys; the French bundle overrides the ready label and inherits the unknown label from the parent.
This program uses class-based ListResourceBundle types compiled in one file. It deliberately selects the root locale and Locale.FRENCH rather than reading the machine default. The result is a reproducible fallback test, not a statement that every application should force French or root labels.
Missing is a visible failure
An absent key throws MissingResourceException even if the bundle itself loaded successfully. That failure should be caught at the display boundary or prevented by a completeness check across supported locales. Returning the key as the label everywhere can hide translation regressions in production.
Bundle discovery has caching and fallback behavior. Hot-editing a resource file does not by itself prove that a running process will reload it. Properties-file encoding also changed across Java releases; a Java 8 deployment needs an explicit encoding review. This fixture avoids that issue by using class-based string values.
Working program
import java.util.ListResourceBundle;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
public class ReceiptMessages {
public static class Labels extends ListResourceBundle {
protected Object[][] getContents() { return new Object[][]{{"ready", "Ready"}, {"unknown", "Unknown"}}; }
}
public static class Labels_fr extends ListResourceBundle {
protected Object[][] getContents() { return new Object[][]{{"ready", "Pret"}}; }
}
public static void main(String[] args) {
ResourceBundle root = ResourceBundle.getBundle(Labels.class.getName(), Locale.ROOT);
ResourceBundle french = ResourceBundle.getBundle(Labels.class.getName(), Locale.FRENCH);
System.out.println(root.getString("ready"));
System.out.println(french.getString("ready"));
System.out.println(french.getString("unknown"));
try { french.getString("deleted"); }
catch (MissingResourceException rejected) { System.out.println("missing rejected"); }
}
}Output
Ready
Pret
Unknown
missing rejectedCosts and boundaries
This bounded fixture has three stored labels and one inherited lookup. A larger deployment pays for resource loading, cached bundle state and fallback lookups; missing-key handling can also add exception costs. Translation completeness and text layout are separate checks. Test labels that grow longer instead of assuming every locale fits the original button.
Common Mistakes
- Do not use translated labels as stored identifiers.
- Loading a bundle does not establish that every requested key exists.
- Do not assume the machine default locale is reproducible across deployments.
Read next
Locale-aware ordering, Text encoding, String equality.
Extend the tested workflow
Continue with Java MessageFormat apostrophes: keep placeholders visible to the parser.
