Java

Preferences API Overview

Introduction

All of the methods that modify preference data are permitted to operate asynchronously. They may return immediately, and changes will eventually propagate to the persistent backing store. The flush method can be used to force updates to the backing store.

The methods in the Preferences class may be invoked concurrently by multiple threads in a single JVM without the need for external synchronization, and the results will be equivalent to some serial execution. If this class is used concurrently by multiple JVMs that store their preference data in the same backing store, the data store will not be corrupted, but no other guarantees are made concerning the consistency of the preference data.

For more details, choose from the following links:


Comparing Preferences API to Other Mechanisms

Prior to the introduction of the Preferences API, developers could choose to manage preference and configuration data in an ad hoc fashion, by using the Properties API or the JNDI API as described below.

Often, preference and configuration data was stored in properties files, accessed through the java.util.Properties API. However, there are no standards as to where such files should reside on disk, or what they should be called. Using this mechanism, it is extremely difficult to backup a user's preference data, or transfer it from one machine to another. As the number of applications increases, the possibility of file name conflicts increases. Also, this mechanism is of no help on platforms that lack a local disk, or where it is desirable that the data be stored in an external data store (such as an enterprise-wide LDAP directory service).

Less frequently, developers stored user preference and configuration data in a directory service, accessed through the Java Naming and Directory Interface (JNDI) API. Unlike the Properties API, JNDI allows the use of arbitrary data stores (back-end neutrality). While JNDI is extremely powerful, it is also rather large, consisting of 5 packages and 83 classes. JNDI provides no policy as to where in the directory name space the preference data should be stored, or in which name space.

Neither Properties nor JNDI provide a simple, ubiquitous, back-end neutral preferences management facility. The Preferences API does provide such a facility, combining the simplicity of Properties with the back-end neutrality of JNDI. It provides sufficient built-in policy to prevent name clashes, foster consistency, and encourage robustness in the face of inaccessibility of the backing data store.


Usage Notes

The material contained in this section is not part of the Preferences API specification, instead it is intended to provide some examples of how the Preferences API might be used.

Obtaining Preferences Objects for an Enclosing Class

The following examples illustrate how you might obtain the Preferences objects (system and user) pertaining to the enclosing class. These examples would work only inside instance methods.

Note that static final fields, rather than inline String literals, are used for the key names (NUM_ROWS and NUM_COLS). This reduces the likelihood of runtime bugs from typographical errors in key names.

Note also that reasonable defaults are provided for each of the preference values obtained. These defaults will be returned if no preference value has been set, or if the backing store is inaccessible.

package com.acme.widget;
import  java.util.prefs.*;

public class Gadget {
    // Preference keys for this package
    private static final String NUM_ROWS = "num_rows";
    private static final String NUM_COLS = "num_cols";

    void foo() {
        Preferences prefs = Preferences.userNodeForPackage(Gadget.class);

        int numRows = prefs.getInt(NUM_ROWS, 40);
        int numCols = prefs.getInt(NUM_COLS, 80);

        ...
    }
}

The above example obtains per-user preferences. If a single, per-system value were desired, the first line in foo would be replaced by:

        Preferences prefs = Preferences.systemNodeForPackage(Gadget.class);

Obtaining Preferences Objects for a Static Method

The examples in the prior section illustrate obtaining Preferences objects pertaining to the enclosing class, and work inside instance methods. In a static method (or static initializer), you need to explicitly provide the name of the package:

    Static String ourNodeName = "/com/acme/widget";

    static void foo() {
        Preferences prefs = Preferences.userRoot().node(ourNodeName);

        ...
    }

It is always acceptable to obtain a system preferences object once, in a static initializer, and use it whenever system preferences are required:

    static Preferences prefs =  Preferences.systemRoot().node(ourNodeName);

In general, it is acceptable to do the same thing for a user preferences object, but not if the code in question is to be used in a server, wherein multiple users will be running concurrently or serially. In such a system, userNodeForPackage and userRoot will return the appropriate node for the calling user, thus it's critical that calls to userNodeForPackage or userRoot be made from the appropriate thread at the appropriate time. If a piece of code may eventually be used in such a server environment, it is good, conservative practice to obtain user preferences objects immediately before they are used, as in the prior example.

Atomic Updates

The preferences API does not provide database like "transactions" wherein multiple preferences are modified atomically. Occasionally, it is necessary to modify two or more preferences as a unit. For example, suppose you are storing the x and y coordinates where a window is to be placed. The only way to achieve atomicity is to store both values in a single preference. Many encodings are possible. Here's a simple one:

    int x, y;
    ...
    prefs.put(POSITION, x + "," + y);

When such a "compound preference" is read, it must be decoded. For robustness, allowances should be made for a corrupt (unparseable) value:

    static int X_DEFAULT = 50, Y_DEFAULT = 25;
    void baz() {
        String position = prefs.get(POSITION, X_DEFAULT + "," + Y_DEFAULT);
        int x, y;
        try {
            int i = position.indexOf(',');
            x = Integer.parseInt(coordinates.substring(0, i));
            y = Integer.parseInt(position.substring(i + 1));
        } catch(Exception e) {
            // Value was corrupt, just use defaults
            x = X_DEFAULT;
            y = Y_DEFAULT;
        }
        ...
    }

Determining Backing Store Status

Typical application code has no need to know whether the backing store is available. It should almost always be available, but if it isn't, the code should continue to execute using default values in place of preference values from the backing store. Very rarely, some advanced program might want to vary its behavior (or simply refuse to run) if the backing store were unavailable. Following is a method that determines whether the backing store is available by attempting to modify a preference value and flush the result to the backing store.

    private static final String BACKING_STORE_AVAIL = "BackingStoreAvail";

    private static boolean backingStoreAvailable() {
        Preferences prefs = Preferences.userRoot().node("<temporary>");
        try {
            boolean oldValue = prefs.getBoolean(BACKING_STORE_AVAIL, false);
            prefs.putBoolean(BACKING_STORE_AVAIL, !oldValue);
            prefs.flush();
        } catch(BackingStoreException e) {
            return false;
        }
        return true;
    }

Copyright © 2006 Sun Microsystems, Inc. All Rights Reserved.

Please send comments to: TBP
Sun
Java Software