Sir, what is your preference?

Manage your application's preferences with J2SE 1.4's new Preferences API

Almost all but the smallest applications have preferences -- a set of values that affect how the application behaves at runtime. Preferences examples range from configuring an MP3 player to sport a skin of your choice, to setting a 3D shooter game's to varying screen resolutions. Now, of course you want the values to be there next time you run the application, so usually they're made persistent, be it in a file, database, or some other storage mechanism.

As an application developer, how and where you put the preferences vary depending on your application, the platform it runs on, and the programming language. If your application will run only on Windows, then Registry is the place to put the preferences. Developers writing applications written in portable C/C++, on the other hand, usually put their preferences in files. Bigger, server-side applications might store them in a database (although usually something needs to be stored in a file -- the connection string to the database, for example).

Therefore, when designing your application, you'll always face the same preference management questions: Where do we store them? How? Is maintaining them easy enough? Can we easily move them to another platform when necessary? If Java is your programming language, you can follow several approaches in answering these questions. I'll compare the traditional approaches, then discuss in detail the latest option -- J2SE (Java 2, Standard Edition) 1.4's new Preferences API. Enjoy!

The old, simple, and unscalable approach

Ahhh -- good old Properties. It has been around since Java's beginning, JDK 1.0 to be exact. It's easy to use: just put the preferences into the Properties object and store them in a file. Later, when you need to get them back, load it from the file, and voila, you have a Properties object containing the values you previously stored in the file. It's as simple as that. What could possibly be wrong with this approach?

The problems with Properties

For simple applications with just a few preferences, java.util.Properties works just fine. However, as the application grows in size, with the Properties API you can develop problems such as:

  • Numerous property files, each assigned to a part of the application (usually, but not always, a package), forcing you to hardcode a gaggle of file names -- or even their paths -- inside your code
  • Several large property files, which force you to prevent name collisions and to track numerous different preferences

To solve the second problem, you'd typically create your own hierarchy out of the flat namespace. That is, you'd create names like:

  • myapp.payment.SSLPort=10256
  • myapp.payment.SETPort=10257
  • myapp.admin.listenPort=10258

Of course, the Properties API possesses no awareness of this hierarchy, so you'd have to add your own code on top of it. However, this approach also proves unsafe: if someone hand-edits the file and replaces a period with a comma in one of the names above, who knows what will happen to your application?

In other words, managing and maintaining preferences using the Properties API can quickly become a nightmare -- it simply doesn't scale! Besides, using the Properties API this way won't work with a platform without a local disk. Now let's take a look at another approach that solves most of these problems.

The powerful, back-end neutral, and (too) complex approach

In the second approach, you store preferences in a directory service through the JNDI (Java Naming Directory Interface) API, thus solving most problems that Properties presents. JNDI-stored preferences, however, generally proves too complex for our purposes. Not all applications designers want anything to do with a directory service; most just need a simple yet elegant way to store preferences.

Besides, you still don't know where exactly in the namespace you should store the preference data. Ideally, you don't want to know where; you just want a place to stuff the data in and get them out when needed!

The new, improved, yet simple approach!

The Preferences API solves problems the two approaches above cause, while keeping the interface simple. The whole package comprises only three interfaces, four classes, and two exceptions. Even better, most of the time, you need care about only one class: Preferences. (Other classes are more relevant to the API's implementers than its users.)

Because the Preferences API is back-end neutral, you need not care whether the data are stored in files, database tables, or a platform-specific storage such as the Windows Registry. In fact, you don't want to care, because the situation may differ across varying platforms, or even across implementations on the same platform.

The Preferences API also offers a simple way to organize your preferences hierarchically and to associate a package with a node. Both prove convenient because organizing your preferences becomes a no-brainer: a set of preferences for a module becomes associated with the respective package, that's it. You don't have to think, "Oh, let's see, for this module, they have to go to this file, and that module, that file, or is it this file? What's the convention again?"

I once enthusiastically told a colleague about the Preferences API, quite sure that he'd be impressed. Expressionless, he listened to me for a while, then asked:

Do you always have to go through the API to access the preferences? We move preferences from platform to platform during testing, and copying the property files over has worked well so far.

Well, I learned two things from his remark: first, impressing colleagues is not easy. Second, the Preferences API does offer an easy solution to his problem. That is, you can export the whole preferences tree, or just a node to XML format. Copy this file to the other machine, import it back, and you don't have to reset all the preferences from scratch!

Some basic concepts

By now you know that with the Preferences API you can organize your preferences in a hierarchical manner. Not only that, it also recognizes that some preferences are shared among users, while some are not.

Trees, trees everywhere

In the Preferences API, you manage preferences in tree-like collections of nodes, which act similarly to directories in a hierarchical file system. The name-value pairs under the nodes, in turn, act roughly similar to files under the directories.

Moreover, you traverse a preference tree similarly to the way you'd traverse a directory tree. At the top of a tree resides a root node with an absolute path name of "/". Accordingly, a node named A under this root node has an absolute path name of "/A", and a node named B under A "/A/B", and so on.

System and user preference trees

The Preferences API recognizes two kinds of preference tree: one shared by all users of the system (that is, the system preference tree), and another specific to a particular user (the user preference tree). The notion of "system" and "user," however, can vary across implementations. For more details, read the sidebar, "Platform Differences."

Put it into use

OK, enough talk about concepts. How do you use the API? The following sections show you how!

Obtain a Preferences object

Of course, before everything else, you have to obtain a Preferences object to work with. Unlike Properties, Preferences doesn't have any accessible constructor. Instead, you obtain the object through one of four static factory methods:

  • public static Preferences systemNodeForPackage(Object o): Returns a node in the system preferences tree corresponding to the passed object's package
  • public static Preferences userNodeForPackage(Object o): Returns a node in the user preferences tree corresponding to the passed object's package
  • public static Preferences systemRoot(): Returns the root of the system preferences tree
  • public static Preferences userRoot(): Returns the root of the user preferences tree

In the list above, the first two methods serve as convenience methods. Let's say you have class C in package P. Passing an instance of C to either of them will return (and if necessary, create) a node whose path corresponds to package P. So, even if the package name changes for some reason, there's no need to change your code. Listing 1 illustrates this point further:

Listing 1.

package com.acme.myapp.user;
import java.util.prefs.Preferences;
public class Payer {
   private static final String PREFERRED_CHANNEL = 
   private PreferredChannel preferredChannel = 
   public void storePreferences() {
      Preferences prefs = Preferences.userNodeForPackage(this);
      prefs.put(PREFERRED_CHANNEL, preferredChannel.toString());
      // The rest of the code here...
   public static void main(String[] args) {
      // The test code
      new Payer().storePreferences();

You'll see the result of running the code above under J2SE Beta 1.4 for Windows in Figure 1. Note that WML is encoded as /W/M/L, and PreferredChannel as /Preferred/Channel because Preferences has case-sensitive keys and node-names, while the backing store of choice (in this case Windows Registry) does not.

Figure 1. A call to userNodeForPackage() creates a node under the user preferences root, with a path that corresponds to the package

The last two factory methods -- systemRoot() and userRoot() -- simply return the system root and the user root, respectively. This might be useful when you don't have any instance to pass in (in static methods, for example), or when you need to create paths manually (that is, independent of the package where the code resides). However, systemRoot() and userRoot() merely return the root nodes. To create and traverse down the branches of the preference tree, you need another method:

public abstract Preferences node(String pathName)

node() accepts either an absolute (begins with a "/") or a relative path name. Note: a Preferences object interprets the latter relative to the node the object corresponds to. That is, if you do this:

Preferences prefs = Preferences.systemRoot().node("/the/first/node");
Preferences prefs2 = prefs.node("the/second/node");
Preferences prefs3 = prefs.node("/yet/another/branch");

you'll get what appears in Figure 2.

Figure 2. The node() method in action

Get and put preferences values

Unlike Properties, Preferences doesn't force you to convert everything into String before putting them in (and likewise, it doesn't force you to retrieve everything as String). Instead, it offers methods in get/put pairs for each of the following types:

  • String
  • boolean
  • byte[]
  • double
  • float
  • int
  • long

However, unlike Properties, Preferences does force you to provide a default value whenever you call one of the get methods. The rationale behind this is that the application should still run even though the backing store is unavailable, so you should provide a reasonable default value if for some reason the previously stored value is irretrievable.

Don't call us, we'll call you

The Observer pattern is a bit like the so-called Hollywood Principle: "Don't call us, we'll call you." I guess aspiring movie stars hate it, but don't we lazy programmers just love it! We're too lazy to check every once in a while whether something has changed, aren't we? We just want notification when it actually happens!

Preferences lets us achieve just what we want: by registering a NodeChangeListener to a node, you'll get notified whenever a child node under this node is added or removed. Not only that, you can also listen for notifications when a preference is added to or removed from the node, or when the value of the preference changes. Let's see the listeners in action in Listing 2:

Listing 2.

import java.util.prefs.*;
public class TestTheListeners {
   public static void main(String[] args) {
      MyListener listener = new MyListener();
      // Create a node for testing 
      Preferences listeningNode = Preferences.userRoot().node("listening");
      try {
         listeningNode.put("key1", "bar");
         listeningNode.putInt("key1", 8276);
         Preferences test1 = listeningNode.node("test1");
         Preferences test2 = test1.node("test2");
      } catch(BackingStoreException bsEx) {
         // Ignore it
      try {
      } catch(InterruptedException iEx) {
         // Ignore it as well
class MyListener implements NodeChangeListener, PreferenceChangeListener {
   public void childAdded(NodeChangeEvent nceEvt) {
      System.out.print("Child: ");
      System.out.print(" added to Parent: ");
   public void childRemoved(NodeChangeEvent nceEvt) {
      System.out.print("Child: ");
      System.out.print(" removed from Parent: ");
   public void preferenceChange(PreferenceChangeEvent pceEvt) {
      System.out.print("Preference change at Node: ");
      System.out.print(" key: " + pceEvt.getKey());
      System.out.println(" value: " + pceEvt.getNewValue());

Running this produces the following result:

1 2 Page 1
Page 1 of 2