Home | FAQ | Contact me

Resource bundles (with properties file)

One day in February, I was studying resource bundles. This is what I learned.

The properties files can't be placed willy-nilly, but must occupy a subdirectory named "resources" in the classpath. In Eclipse, this means that the resources directory gets created as a source directory. I accomplished this by:

  1. Create a new package named resources under the src directory (of the parent project to this demo code).
  2.  
  3. Right-click on resources, choose Build Path->Use as Source Folder. Visually (in Eclipse's Package Explorer pane), this results in an entity, src/resources, with an icon identical to the one already gracing the src subdirectory.
  4.  
  5. Create a new properties file named demo_en.properties and add the following contents: key = This is the key to my office.
  6.  
  7. Access this bundle (properties file) via the code in this demo example.
  8.  
  9. Create other properties files (i.e.: resources named demo_fr.properties, demo_de.properties and even demo.properties as desired.

To ResourceBundle.getBundle() a resource name is passed in which to find a properties file to open, read and to which a ResourceBundle is returned. This bundle can be exploited by calls to the getString() method.

Typically, a bundle isn't constructed (in the object-oriented sense), but created via the call to (static) getBundle().

The ability to specify locale also exists, yielding a translation capability as also illustrated here. In fact, it's rather important to set a locale when writing applications in Java because not to set one means you'll consume the default locale of the host on which your program is running. There are a lot of Java classes that live and die by what the current application locale is, InputReader for instance, and their behavior could become unpredictable as a result.

Notice that great care is taken against referencing null: there is nothing magically solid about this interface.

Last, we show how to enumerate all the keys in a bundle. This would also work for Properties. Notice how Java 1.5 generics gets us around having to coerce the return from nextElement() to String.

See http://java.sun.com/j2se/1.4.2/docs/api/java/util/ResourceBundle.html

Inside this code

In terms of beginning Java samples you have:

  • Use of ResourceBundle class.
  • Sample use of locale settings in Eclipse.
  • Even simpler and more practical example at end.
Eclipse tip: Where's the library (JAR)?

In Dynamic Web Projects, you can't see libraries maintained on the path project-name/WebContent/WEB-INF/lib in the Package Explorer pane. To see them, open the Navigator: Window->Show View->Navigator.

If you have a separate project with "library" code and the output is a JAR copied to your main project by an ant script, you'll need to commit the JAR in place of the older version, so it's crucial to find it unless you do a commit from higher up and uncheck all the files and directories you don't want to commit.

ResBundleDemo.java:
package com.etretatlogiciels.samples.resbundle;

import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Enumeration;
import java.util.Locale;

/**
 * Demonstration of the ResourceBundle package that reads property files and
 * coughs up strings therefrom.
 *
 * @author Russell Bateman, February 2009
 */
public class ResBundleDemo
{
  private static final String  RESOURCE = "properties/demo";

  public static void main( String[] args )
  {
    Locale          defloc = Locale.getDefault();
    Locale          frn    = new Locale( "fr" );
    String          locale = defloc.toString();
    ResourceBundle  bundle = null;

    System.out.print( "Our default system locale is " );
    if( locale != null )
      System.out.println( locale + "." );
    else
      System.out.println( "(broken--very unlikely)." );

    /* We're running in en_US, here's how we get the string associated with "key"
     * out of "resources/demo_en.properties".
     */
    try
    {
      bundle = ResourceBundle.getBundle( RESOURCE );
    }
    catch( MissingResourceException e )
    {
      e.printStackTrace();
      System.out.println( "No bundle \"" + RESOURCE + "\" was found.");
    }

    if( bundle != null )
    {
      System.out.println( "Bundle resources name is: \""
                          + bundle.toString()
                          + "\"" );
      String  string = bundle.getString( "key" );

      if( string == null )
        string = "Key \"key\" did not exist in " + RESOURCE;

      System.out.println( "The bundle key \"key\" has the value \""
                          + string
                          + "\"" );
    }

    /* This is very instructive: it gets us the contents of "demo.properties"
     * instead of "demo_en.properties".
     */
    System.out.println( "\nLet's try out a null locale "
                        + "(uses demo.properties)..." );
    Locale  nullLocale = new Locale( "" );
    Locale.setDefault( nullLocale );

    try
    {
      bundle = ResourceBundle.getBundle( RESOURCE, nullLocale );
    }
    catch( MissingResourceException e )
    {
      e.printStackTrace();
      System.out.println( "No bundle \"" + RESOURCE + "\" was found.");
    }

    if( bundle != null )
    {
      String  string = bundle.getString( "key" );

      if( string == null )
        string = "Key \"key\" did not exist in " + RESOURCE;

      System.out.println( "The bundle key \"key\" has the value \""
                          + string
                          + "\"" );
    }

    /* Now, let's try this in French just to prove our point!
     */
    System.out.println( "\nAnd now, in French! (uses demo_fr.properties)");
    Locale.setDefault( frn );

    try
    {
      bundle = ResourceBundle.getBundle( RESOURCE );
    }
    catch( MissingResourceException e )
    {
      e.printStackTrace();
      System.out.println( "Aucun faisceau \"" + RESOURCE + "\" n'existe.");
    }

    if( bundle != null )
    {
      String  string = bundle.getString( "key" );

      if( string == null )
        string = "Clef  \"key\" n'existe pas dans " + RESOURCE;

      System.out.println( "La clef de faisceau \"key\" se compose de \""
                          + string
                          + "\"" );
    }

    /* Now, let's try this in English again to demonstrate enumeration.
     */
    Locale  eng    = new Locale( "en" );
    System.out.println( "\nAnd now, let's enumerate keys "
                        + "(uses demo_en.properties)");
    Locale.setDefault( eng );

    try
    {
      bundle = ResourceBundle.getBundle( RESOURCE );
    }
    catch( MissingResourceException e )
    {
      e.printStackTrace();
      System.out.println( "No bundle \"" + RESOURCE + "\" was found.");
    }

    if( bundle != null )
    {
      Enumeration< String >  keys = bundle.getKeys();

      if( keys == null )
      {
        System.out.println( "Error attempting to enumerate keys in this bundle" );
      }
      else
      {
        while( keys.hasMoreElements() )
        {
          //String  k = ( String ) keys.nextElement();
          String  k = keys.nextElement();
          String  v = bundle.getString( k );

          System.out.println( "  " + k + " = " + v );
      	}
      }
    }
  }
}
// vim: set tabstop=2 shiftwidth=2 noexpandtab:

Properties files

These are kept under a package directory named properties in Eclipse as illustrated on the right.

These files have the following content:

demo.properties
# This is a properties file opened and consumed by ResBundleDemo. Except that it # is not! This 'key' should not be the answer because, in the default en_US # locale, resource file "demo_en.properties" will be chosen instead. Only if that # file did not exist, would "demo.properties" be used instead. key = (waves hand) This is not the key you're looking for
demo_en.properties
# This is a properties file opened and consumed by ResBundleDemo. key = This is the key to my office. skeleton = This is a freaky key to be sure. Smee = Captain Hook's right-hand man
demo_fr.properties
# Voici le fichier des aiguillages consomme par ResBundleDemo. key = Voici la clef de mon bureau.

Console output

Our default system locale is en_US. Bundle resources name is: "java.util.PropertyResourceBundle@1a46e30" The bundle key "key" has the value "This is the key to my office." Let's try out a null locale (uses demo.properties)... The bundle key "key" has the value "(waves hand) This is not the key you're looking for." And now, in French! (uses demo_fr.properties) La clef de faisceau "key" se compose de "Voici la clef de mon bureau." And now, let's enumerate keys (uses demo_en.properties) Smee = Captain Hook's right-hand man! skeleton = This is a freaky key to be sure. key = This is the key to my office.

The simple approach

The slightly more elaborate arrangements made above might go beyond what you need. Here's the minimal approach:

  1. Create a new file, messages.properties, directly in the src folder of your project (not under any package).
  2.  
  3. Fill the new file with your messages.
  4.  
  5. Create localized versions of this file such as messages_fr.properties, messages_es.properties, etc.
  6.  
  7. Create a static class as a utility for fetching messages, e.g.: AppResources below. It should be obvious from the test code (in main()) how to use this throughout your application code.
    AppResources.java:
    package com.etretatlogiciels.bundles;
    
    import java.util.Locale;
    import java.util.MissingResourceException;
    import java.util.ResourceBundle;
    
    import org.apache.log4j.Logger;
    
    /**
     * Static interfaces to handle all messages, prompts and any other resources we
     * can think of including permitting us to localize them to other languages.
     */
    public class AppResources
    {
    	private static Logger			log = Logger.getLogger( AppResources.class );
    
    	private static final String		MESSAGES      = "messages";
    	private static String			status        = null;
    	private static Locale			defaultLocale = null;
    	private static ResourceBundle	bundle        = null;
    
    	static
    	{
    		defaultLocale = Locale.getDefault();
    		bundle        = ResourceBundle.getBundle( MESSAGES );
    
    		if( bundle == null )
    			status = "Bundle not found";
    	}
    
    	public static String getStatus() { return status; }
    
    	/**
    	 * Change locales, to another language.
    	 *
    	 * @param newLocale - the new locale, as created using new Locale( "fr" )
    	 * 			and passed in; if null return to start-up locale
    	 * @return true/false that the locale change worked out
    	 */
    	public static boolean changeLocale( Locale newLocale )
    	{
    		if( newLocale == null )
    			newLocale = defaultLocale;
    
    		Locale.setDefault(  newLocale );
    
    		try
    		{
    			bundle = ResourceBundle.getBundle( MESSAGES );
    		}
    		catch( MissingResourceException e )
    		{
    			log.trace( "changeLocale(): Unable to find message bundle for ");
    			return false;
    		}
    
    		return true;
    	}
    
    	/**
    	 * Fetch the message or prompt we need.
    	 *
    	 * @param key - to the message or prompt looked for.
    	 * @return the message or prompt wanted.
    	 */
    	public static final String getMessage( String key )
    	{
    		return bundle.getString( key );
    	}
    
    	/**
    	 * Quick and dirty test to work out the kinks the first time without
    	 * raising this to the level of a formal unit test.
    	 *
    	 * @param args (unused)
    	 */
    	public static void main( String[] args )
    	{
    		System.out.println( "A message in the start-up locale: " );
    		System.out.println( AppResources.getMessage( "startup.error" ) );
    
    		System.out.println( "If any messages remain in English, it's because "
    					+ "their localization has been botched.");
    
    		System.out.println( "\nThe same message in French-France:" );
    		Locale	frn = new Locale( "fr" );
    		AppResources.changeLocale( frn );
    		System.out.println( AppResources.getMessage( "startup.error" ) );
    
    		System.out.println( "\nThe same message in Spanish-Spain:" );
    		Locale	esp = new Locale( "es" );
    		AppResources.changeLocale( esp );
    		System.out.println( AppResources.getMessage( "startup.error" ) );
    
    		System.out.println( "\nThe same message in Finnish (which we won't do "
    					+ "so it demonstrates what happens" );
    		System.out.println( "   when a locale/language isn't supported):" );
    		Locale	suo = new Locale( "fi" );
    		AppResources.changeLocale( suo );
    		System.out.println( AppResources.getMessage( "startup.error" ) );
    	}
    }
    	

    Here's the output from the test above:

    A message in the start-up locale: There must be test fodder to run this test If any messages remain in English, it's because their localization has been botched. The same message in French-France: Des données doivent exister pour effectuer cet essai The same message in Spanish-Spain: Datos deben existir para efectuar este ensayo The same message in Finnish (which we won't do so it demonstrates what happens when a locale/language isn't supported): There must be test fodder to run this test