Preferences
Overview
Applications require preference and configuration data to adapt to the needs of different users and environments and to store and retrieve user and system preference and configuration data. The data is stored persistently in an implementation-dependent backing store. There are two separate trees of preference nodes, one for user preferences and one for system preferences.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...
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
- Obtaining Preferences Objects for a Static Method
- Atomic Updates
- Determining Backing Store Status
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(this); 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(this);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(""); try { boolean oldValue = prefs.getBoolean(BACKING_STORE_AVAIL, false); prefs.putBoolean(BACKING_STORE_AVAIL, !oldValue); prefs.flush(); } catch(BackingStoreException e) { return false; } return true; }
Design FAQ
Following is a collection of frequently asked questions concerning the design of the Preferences API.
- How does this Preferences API relate to Properties API?
It is intended to replace most common uses of Properties, rectifying many of its deficiencies, while retaining its light weight. When using Properties, the programmer must explicitly specify a pathname for each properties file, but there is no standard location or naming convention. Properties files are "brittle", as they are hand-editable but easily corrupted by careless editing. Support for non-string data types in properties is non-existent. Properties cannot easily be used with a persistence mechanism other than the file system. In sum, the Properties facility does not scale.
- How does Preferences API relate to JNDI?
Like JNDI, it provides back-end neutral access to persistent key-value data. JNDI, however, is far more powerful, and correspondingly heavyweight. JNDI is appropriate for enterprise applications that need its power. Preferences API is intended as a simple, ubiquitous, back-end neutral preferences-management facility, enabling any Java application to easily tailor its behavior to user preferences and maintain small amounts of state from run to run.
- Why do all of the get methods require the caller to pass in a default?
This forces the application authors to provide reasonable default values, so that applications have a reasonable chance of running even if the repository is unavailable.
- How was it decided which methods should throw BackingStoreException?
Only methods whose semantics absolutely require the ability to communicate with the backing store throw this exception. Typical applications will have no need to call these methods. As long as these methods are avoided, applications will be able to run even if the backing store is unavailable, which was an explicit design goal.
- Why doesn't this API provide stronger guarantees concerning concurrent access by multiple VMs? Similarly, why doesn't the API allow multiple Preferences updates to be combined into a single "transaction", with all or nothing semantics?
While the the API does provide rudimentary persistent data storage, it is not intended as a substitute for a database. It is critical that it be possible to implement this API atop standard preference/configuration repositories, most of which do not provide database-like guarantees and functionality. Such repositories have proven adequate for the purposes for which this API is intended.
- Why does this API have case-sensitive keys and node-names, while other APIs playing in a similar space (such as the Microsoft Windows Registry and LDAP) do not?
In the Java programming universe, case-sensitive String keys are ubiquitous. In particular, they are provided by the Properties class, which this API is intended to replace. It is not uncommon for people to use Properties in a fashion that demands case-sensitivity. For example, Java package names (which are case-sensitive) are sometimes used as keys. It is recognized that this design decision complicates the life of the systems programmer who implements Preferences atop a backing store with case-insensitive keys, but this is considered an acceptable price to pay, as far more programmers will use the Preferences API than will implement it.
- Why doesn't this API use the Java 2 Collections Framework?
This API is designed for a very particular purpose, and is optimized for that purpose. In the absence of generic types (see JSR-14), the API would be less convenient for typical users. It would lack compile-time type safety, if forced to conform to the Map API. Also, it is not anticipated that interoperability with other Map implementations will be required (though it would be straightforward to implement an adapter class if this assumption turned out to be wrong). Preferences API is, by design, so similar to Map that programmers familiar with the latter should have no difficulties using the former.
- Why don't the put and remove methods return the old values?
It is desirable that both of these methods be executable even if the backing store is unavailable. This would not be possible if they were required to return the old value. Further, it would have negative performance impact if the API were implemented atop some common back-end data stores.
- Why does the API permit, but not require, stored defaults?
This functionality is required in enterprise settings for scalable, cost-effective administration of preferences across the enterprise, but would be overkill in a self-administered single-user setting.
- Why doesn't this API contain methods to read and write arbitrary serializable objects?
Serialized objects are somewhat fragile: if the version of the program that reads such a property differs from the version that wrote it, the object may not deserialize properly (or at all). It is not impossible to store serialized objects using this API, but we do not encourage it, and have not provided a convenience method.
- Why is Preferences an abstract class rather than an interface?
It was decided that the ability to add new methods in an upward compatible fashion outweighed the disadvantage that Preferences cannot be used as a "mixin" (That is to say, arbitrary classes cannot also be made to serve as Preferences objects.) Also, this obviates the need for a separate class for the static methods. (Interfaces cannot contain static methods.)