Using preferences, Adding event listeners and Getting sub-nodes of Preferences


Using preferences

Preferences can be used to store user settings that reflect a user's personal application settings, e.g. their editor font, whether they prefer the application to be started in full-screen mode, whether they checked a "don't show this again" checkbox and things like that.

public class CustomExitConfirmer {
    private static boolean shouldConfirmExit() {
        Preferences customPreferences = Preferences.userNodeForPackage(CustomExitConfirmer.class);
        boolean shouldShowDialog = customPreferences.getBoolean("displayExitConfirmation", true); // true is default value
 
        if (!shouldShowDialog) {
            return true;
        }
 
        //
        // Show a dialog here...
        //
 
        boolean exitConfirmed = ...; // whether the user clicked OK or Cancel
        boolean doNotShowAgain = ...; // get value from "Do not show again" checkbox
 
        if (exitConfirmed && doNotShowAgain) {
            // Exit was confirmed, and the user chose not to show the dialog again
            // Save these settings to the Preferences object so the dialog will not show again next time
            customPreferences.putBoolean("displayExitConfirmation", false);
        }
 
        return exitConfirmed;
    }
 
    public static void exitProgram() {
        if (shouldConfirmExit()) {
            System.exit(0);
        }
    }
}

Adding event listeners

There are two types of events emitted by a Preferences object: PreferenceChangeEvent and NodeChangeEvent.

PreferenceChangeEvent

A PreferenceChangeEvent gets emitted by a Properties object every time one of the node's key-value-pairs changes. PreferenceChangeEvents can be listened for with a PreferenceChangeListener:

Version ≥ Java SE 8
preferences.addPreferenceChangeListener(changeEvent -> {
    String updatedValue = changeEvent.getNewValue();
    String modifiedKey = changeEvent.getKey();
    Preferences modifiedPreferencesNode = changeEvent.getNode();
});
 
Version < Java SE 8
preferences.addPreferenceChangeListener(new PreferenceChangeListener() {
    @Override
    public void preferenceChange(PreferenceChangeEvent changeEvent) {
        String updatedValue = changeEvent.getNewValue();
        String modifiedKey = changeEvent.getKey();
        Preferences modifiedPreferencesNode = changeEvent.getNode();
    }
});

This listener will not listen to changed key-value pairs of child nodes.

NodeChangeEvent

This event will be fired whenever a child node of a Properties node is added or removed.

preferences.addNodeChangeListener(new NodeChangeListener() {
    @Override
    public void childAdded(NodeChangeEvent changeEvent) {
        Preferences addedNode = changeEvent.getChild();
        Preferences parentNode = changeEvent.getParent();
    }
 
    @Override
    public void childRemoved(NodeChangeEvent changeEvent) {
        Preferences removedNode = changeEvent.getChild();
        Preferences parentNode = changeEvent.getParent();
    }
});

Getting sub-nodes of Preferences

Preferences objects always represent a specific node in a whole Preferences tree, kind of like this:

rRoot ├── com │ └── mycompany │ └── myapp │ ├── darkApplicationMode=true │ ├── showExitConfirmation=false │ └── windowMaximized=true └── org └── myorganization └── anotherapp ├── defaultFont=Helvetica ├── defaultSavePath=/home/matt/Documents └── exporting ├── defaultFormat=pdf └── openInBrowserAfterExport=false

To select the /com/mycompany/myapp node:

  • By convention, based on the package of a class:
  • package com.mycompany.myapp;
     
    // ...
     
    // Because this class is in the com.mycompany.myapp package, the node
    // /com/mycompany/myapp will be returned.
    Preferences myApp = Preferences.userNodeForPackage(getClass());
  • By relative path:
  • Preferences myApp = Preferences.userRoot().node("com/mycompany/myapp");

    Using a relative path (a path not starting with a /) will cause the path to be resolved relative to the parent node it is resolved on. For example, the following example will return the node of the path /one/two/three/com/mycompany/myapp:

    Preferences prefix = Preferences.userRoot().node("one/two/three");
    Preferences myAppWithPrefix = prefix.node("com/mycompany/myapp");
    // prefix is /one/two/three
    // myAppWithPrefix is /one/two/three/com/mycompany/myapp
  • By absolute path:
  • Preferences myApp = Preferences.userRoot().node("/com/mycompany/myapp");

    Using an absolute path on the root node will not be different from using a relative path. The difference is that, if called on a sub-node, the path will be resolved relative to the root node.

    Preferences prefix = Preferences.userRoot().node("one/two/three");
    Preferences myAppWitoutPrefix = prefix.node("/com/mycompany/myapp");
    // prefix is /one/two/three
    // myAppWitoutPrefix is /com/mycompany/myapp

Coordinating preferences access across multiple application instances

All instances of Preferences are always thread-safe across the threads of a single Java Virtual Machine (JVM). Because Preferences can be shared across multiple JVMs, there are special methods that deal with synchronizing changes across virtual machines.

If you have an application which is supposed to run in a single instance only, then no external synchronization is required.

If you have an application which runs in multiple instances on a single system and therefore Preferences access needs to be coordinated between the JVMs on the system, then the sync() method of any Preferences node may be used to ensure changes to the Preferences node are visible to other JVMs on the system:

// Warning: don't use this if your application is intended
// to only run a single instance on a machine once
// (this is probably the case for most desktop applications)
try {
	preferences.sync();
} catch (BackingStoreException e) {
	// Deal with any errors while saving the preferences to the backing storage
	e.printStackTrace();
}

Basic Programs