The Preferences Class

by: Thomas Paul

Whenever we needed to store user preferences (such as the four most recently used files) we could use the Properties class. The problem with the Properties class is that it makes assumptions about where user preferences should be stored. The basic assumption is that the file system would be used to store this information but this raises several issues. Where in the file system should this data be stored? In the application directory, perhaps? What if the user did not have write access to that directory? What if the user was running on a network computer?

The Preferences API, which is new to Java 1.4, was written to solve these problems. The purpose of the API is to allow for the easy storage of user preferences without the developer having to worry about where this information will be stored. The API determines the appropriate place. For example, when running an application in Windows, the preferences will be stored in the Windows registry.

The API itself is found in the java.util.prefs package and is very easy to use. All preferences are stored in a tree-like structure with the data itself stored in nodes of the tree. The nodes contain name-value pairs with the actual preference data. The API allows for two different types of preference data. System data is shared among all users while User data is used by the current user only. In this way, multiple users on the same device can have their own preferences.

Getting a Preferences Object


Before you do anything with preferences you must get a Preferences object. Preferences is an abstract class so it can't be instantiated directly. Instead you have to run one of the static methods of the Preferences class that will return a real Preferences object. There are two ways to do this. The first method is to manually define a tree structure. The second is to allow your package structure to define the tree structure. The second options should not be used if your class is not in a package since the node will be "unnamed" and shared by every class not in a package.

In order to manually define a tree structure, we need to first get a Preferences object. The Preferences class has a static method to get either the System or User root preferences object:
Preferences prefs = Preferences.userRoot();
Preferences prefs = Preferences.systemRoot();

Once we have our root Preferences object, we can then get our node Preferences object by specifying the node we wish to use. We can combine the two requests into one statement:
Preferences prefs = Preferences.userRoot().node("com/javaranch/prefs");
Preferences prefs = Preferences.systemRoot().node("com/javaranch/prefs");

The second option is to use a Preferences method which will use the name of the package that a class is in to determine the nodes of the tree.

Preferences prefs = Preferences.userNodeForPackage(PrefsTest.class);
Preferences prefs = Preferences.systemNodeForPackage(PrefsTest.class);


Note: The beta version of the Preferences API allowed you to specify an object (such as "this") as a parameter but the final version of the API requires that a class object be specified.

The node created by this command will be determined by the PrefsTest package name. If PrefsTest is in package "com/tom/test", then the node created will be the same as if we had specified:

Preferences.userRoot().node("com/tom/test");

Now that we have a Preferences object, we can use it to store and retrieve user preference information.

Retrieving and Storing Preferences Data

The Preferences class has several methods used to get and put data in the Preferences data store. You can use only the following types of data:

There is a get and put method for each type. The get format is:

type getType(String key, type defaultValue)

So for the boolean, you would code:
boolean getValue = getBoolean("StoredBoolean", false);

For a byte array you would use:
byte[] getValue = getByteArray("StoredBoolean", new byte[0]);

The method name for the String version does not use the type in the name so to get a string you would use this form:
String getValue = get("StoredString", "");

The get methods require that a default value be supplied. If no entry is found in the Preferences data store, the default value will be returned.

The put methods are very similar. The form of the put methods are:

putType(String key, type value)

Here's an example of using preferences to retrieve the user's preferred number of rows to display and the preferred color to display the rows in for an application:


int numRows = prefs.getInt("Rows", 40);
String color = prefs.get("Color", "Blue");

If the user had not previously saved any preferences the defaults of 40 rows and the color blue will be used. After the user changes the preferences we can store them easily:
prefs.putInt("Rows", numRows);
prefs.put("Color", userColor);      

Importing and Exporting Preferences


It may become necessary to save the user's preferences into a file. The Preferences class has methods to copy the preference data into an XML file. The exportNode method will take the specified node and copy it to the specified OutputStream in XML format. The format of the exportNode method:

void exportNode(OutputStream stream);

To create an XML file from our preferences we would do something like this:
prefs.exportNode(stream);

The output of this command will look something like the following:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE preferences SYSTEM 'http://java.sun.com/dtd/preferences.dtd'>
<preferences EXTERNAL_XML_VERSION="1.0">
  <root type="user">
    <map />
    <node name="com">
      <map />
      <node name="javaranch">
        <map />
        <node name="preferences">
          <map>
            <entry key="Rows" value="80" />
            <entry key="Color" value="Red" />
          </map>
        </node>
      </node>
    </node>
  </root>
</preferences>

You can read this into a Preferences object by using the importPreferences method:

void importPreferences(InputStream stream);

Sample Program


Here is a simple program you can use as a sample. When you run it the first time, it will display the default values of 40, and blue. When you run it the second time, it will display the new values of 80 and red. In addition, the program will create an XML file and write it to, "prefs.xml".
import java.util.prefs.*;
import java.io.*;

public class PrefsTest{

    public static void main(String[] args) throws Exception {
        Preferences prefs = Preferences.userRoot().node("com/javaranch/preferences");

        int numRows = prefs.getInt("Rows", 40);
        String color = prefs.get("Color", "Blue");

        new PrefsTest().putPrefs(prefs);

        System.out.println("Color:" + color);
        System.out.println("Rows:" + numRows);

        OutputStream stream = new FileOutputStream("prefs.xml");
        prefs.exportNode(stream);
    }

    public void putPrefs(Preferences prefs) {
        prefs.putInt("Rows", 80);
        prefs.put("Color", "Red");
    }
}


If you are running under Windows, after running this program you can check the Windows registry and see the changes that were made. Look under the node, "HKEY_CURRENT_USER/Software/JavaSoft". There you should find "Prefs\com\javaranch\preferences". At the lowest node, you should find the keys containing the stored preferences.

Conclusion


The Preferences API has some other features which we haven't discussed. For example, you can set Listeners to listen for node value changes. More information on this can be found on the Sun web site at: http://developer.java.sun.com/developer/technicalArticles/releases/preferences/

If you have been using the Properties class to store user preference data in your applications you may want to start using the Preferences API. It is generally easier to use and it is much more portable.