The Preferences API

The Java Preferences API accommodates the need to store both system and per-user configuration data persistently across executions of the Java VM. The Preferences API is like a portable version of the Windows registry, a mini-database in which you can keep small amounts of information, accessible to all applications. Entries are stored as name/value pairs, where the values may be of several standard types including strings, numbers, Booleans, and even short byte arrays. We should stress that the Preferences API is not intended to be used as a true database and you can’t store large amounts of data in it.

Preferences are stored logically in a tree. A preferences object is a node in the tree located by a unique path. You can think of preferences as files in a directory structure; within the file are stored one or more name/value pairs. To store or retrieve items, you ask for a preferences object for the correct path. Here is an example; we’ll explain the node lookup shortly:

    Preferences prefs = Preferences.userRoot().node("oreilly/learningjava");

    prefs.put("author", "Niemeyer");
    prefs.putInt("edition", 4);

    String author = prefs.get("author", "unknown");
    int edition = prefs.getInt("edition", -1);

In addition to the String and int type accessors, there are the following get methods for other types: getLong(), getFloat(), getDouble(), getByteArray(), and getBoolean(). Each of these get methods takes a key name and default value to be used if no value is defined. And, of course, for each get method, there is a corresponding “put” method that takes the name and a value of the corresponding type. Providing defaults in the get methods is mandatory. The intent is for applications to function even if there is no preference information or if the storage for it is not available, as we’ll discuss later.

Preferences are stored in two separate trees: system preferences and user preferences. System preferences are shared by all users of the Java installation. But user preferences are maintained separately for each user; each user sees his or her own preference information. In our example, we used the static method userRoot() to fetch the root node (preference object) for the user preferences tree. We then asked that node to find the child node at the path oreilly/learningjava, using the node() method. The corresponding systemRoot() method provides the system root node.

The node() method accepts either a relative or an absolute path. A relative path asks the node to find the path relative to itself as a base. We also could have gotten our node this way:

    Preferences prefs =
       Preferences.userRoot().node("oreilly").node("learningjava");

But node() also accepts an absolute path, in which case the base node serves only to designate the tree that the path is in. We could use the absolute path /oreilly/learningjava as the argument to any node() method and reach our preferences object.

Preferences for Classes

Java is an object-oriented language, and so it’s natural to wish to associate preference data with classes. In Chapter 12, we’ll see that Java provides special facilities for loading resource files associated with class files. The Preferences API follows this pattern by associating a node with each Java package. Its convention is simple: the node path is just the package name with the dots (.) converted to slashes (/). All classes in the package share the same node.

You can get the preference object node for a class using the static Preferences.userNodeForPackage() or Preferences.systemNodeForPackage() methods, which take a Class as an argument and return the corresponding package node for the user and system trees, respectively. For example:

    Preferences datePrefs = Preferences.systemNodeForPackage( Date.class );
    Preferences myPrefs = Preferences.userNodeForPackage( MyClass.class );
    Preferences morePrefs =
        Preferences.userNodeForPackage( myObject.getClass() );

Here, we’ve used the .class construct to refer to the Class object for the Date class in the system tree and to our own MyClass class in the user tree. The Date class is in the java.util package, so we’ll get the node /java/util in that case. You can get the Class for any object instance using the getClass() method.

Preferences Storage

There is no need to “create” nodes. When you ask for a node, you get a preferences object for that path in the tree. If you write something to it, that data is eventually placed in persistent storage, called the backing store. The backing store is the implementation-dependent storage mechanism used to hold the preference data. All the put methods return immediately, and no guarantees are made as to when the data is actually stored. You can force data to the backing store explicitly using the flush() method of the Preferences class. Conversely, you can use the sync() method to guarantee that a preferences object is up-to-date with respect to changes placed into the backing store by other applications or threads. Both flush() and sync() throw a BackingStoreException if data cannot be read or written for some reason.

You don’t have to create nodes, but you can test for the existence of a data node with the nodeExists() method, and you can remove a node and all its children with the removeNode() method. To remove a data item from a node, use the remove() method, specifying the key; or you can remove all the data from a node with the clear() method (which is not the same as removing the node).

Although the details of the backing store are implementation-dependent, the Preferences API provides a simple import/export facility that can read and write parts of a preference tree to an XML file. (The format for the file is available at http://java.sun.com/dtd/.) A preference object can be written to an output stream with the exportNode() method. The exportSubtree() method writes the node and all its children. Going the other way, the static Preferences.importPreferences() method can read the XML file and populate the appropriate tree with its data. The XML file records whether it is user or system preferences, but user data is always placed into the current user’s tree, regardless of who generated it.

It’s interesting to note that because the import mechanism writes directly to the tree, you can’t use this as a general data-to-XML storage mechanism (other APIs play that role). Also, although we said that the implementation details are not specified, it’s interesting how things really work in the current implementation. On some systems, Java creates a directory hierarchy for each tree at $JAVA_HOME/jre/.systemPrefs and $HOME/.java/.userPrefs, respectively. In each directory, there is an XML file called prefs.xml corresponding to that node.

Change Notification

Often your application should be notified if changes are made to the preferences while it’s running. You can get updates on preference changes using the PreferenceChangeListener and NodeChangeListener interfaces. These interfaces are examples of event listener interfaces, and we’ll see many examples of these in Chapters 16 through 18. We’ll also talk about the general pattern later in this chapter in the section Observers and Observables. For now, we’ll just say that by registering an object that implements PreferenceChangeListener with a node, you can receive updates on added, removed, and changed preference data for that node. The NodeChangeListener allows you to be told when child nodes are added to or removed from a specific node. Here is a snippet that prints all the data changes affecting our /oreilly/learningjava node:

    Preferences prefs =
        Preferences.userRoot().node("/oreilly/learningjava");

    prefs.addPreferenceChangeListener( new PreferenceChangeListener() {
        public void preferenceChange(PreferenceChangeEvent e) {
            System.out.println("Value: " + e.getKey()
                + " changed to "+ e.getNewValue() );
        }
    } );

In brief, this example listens for changes to preferences and prints them. If this example isn’t immediately clear, it should be after you’ve read about events in Chapter 16 and beyond.

Get Learning Java, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.