Preferences in Java

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 ExitConfirmer {
    private static boolean confirmExit() {
        Preferences preferences = Preferences.userNodeForPackage(ExitConfirmer.class);
        boolean doShowDialog = preferences.getBoolean("showExitConfirmation", true); // true is
default value

        if (!doShowDialog) {
        return true;
        }

        //
        // Show a dialog here…
        //
        boolean exitWasConfirmed = …; // whether the user clicked OK or Cancel
        boolean doNotShowAgain = …; // get value from "Do not show again" checkbox

       if (exitWasConfirmed && doNotShowAgain) {
           // Exit was confirmed and the user chose that the dialog should not be shown again
           // Save these settings to the Preferences object so the dialog will not show again next time

           preferences.putBoolean("showExitConfirmation", false);
       }

       return exitWasConfirmed;
   }
   public static void exit() {
        if (confirmExit()) {
            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(evt -> {
    String newValue = evt.getNewValue();
    String changedPreferenceKey = evt.getKey();
    Preferences changedNode = evt.getNode();
});

Version < Java SE 8

preferences.addPreferenceChangeListener(new PreferenceChangeListener() {
    @Override
    public void preferenceChange(PreferenceChangeEvent evt) {
         String newValue = evt.getNewValue();
         String changedPreferenceKey = evt.getKey();
         Preferences changedNode = evt.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 evt) {
          Preferences addedChild = evt.getChild();
          Preferences parentOfAddedChild = evt.getParent();
     }

     @Override
     public void childRemoved(NodeChangeEvent evt) {
          Preferences removedChild = evt.getChild();
          Preferences parentOfRemovedChild = evt.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:

  1. 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());
  1. 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

3. 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();
}

Exporting preferences

Preferences nodes can be exported into a XML document representing that node. The resulting XML tree can be imported again. The resulting XML document will remember whether it was exported from the user or system Preferences.

To export a single node, but not its child nodes:

Version ≥ Java SE 7
try (OutputStream os = …) {
     preferences.exportNode(os);
} catch (IOException ioe) {
     // Exception whilst writing data to the OutputStream
     ioe.printStackTrace();
} catch (BackingStoreException bse) {
     // Exception whilst reading from the backing preferences store
     bse.printStackTrace();
}

Version < Java SE 7

OutputStream os = null;
try {
     os = …;
     preferences.exportSubtree(os);
} catch (IOException ioe) {
     // Exception whilst writing data to the OutputStream
     ioe.printStackTrace();
} catch (BackingStoreException bse) {
     // Exception whilst reading from the backing preferences store
     bse.printStackTrace();
} finally {
     if (os != null) {
         try {
              os.close();
         } catch (IOException ignored) {}
     }
}

To export a single node with its child nodes:

Version ≥ Java SE 7
try (OutputStream os = …) {
     preferences.exportNode(os);
} catch (IOException ioe) {
     // Exception whilst writing data to the OutputStream
     ioe.printStackTrace();
} catch (BackingStoreException bse) {
     // Exception whilst reading from the backing preferences store
     bse.printStackTrace();
}

Version < Java SE 7

OutputStream os = null;
try {
     os = …;
     preferences.exportSubtree(os);
} catch (IOException ioe) {
     // Exception whilst writing data to the OutputStream
     ioe.printStackTrace();
} catch (BackingStoreException bse) {
     // Exception whilst reading from the backing preferences store
     bse.printStackTrace();
} finally {
     if (os != null) {
         try {
             os.close();
         } catch (IOException ignored) {}
     }
}

Importing preferences

Preferences nodes can be imported from a XML document. Importing is meant to be used in conjunction with the exporting functionality of Preferences, since it creates the correct corresponding XML documents.

The XML documents will remember whether they were exported from the user or system Preferences. Therefore, they can be imported into their respective Preferences trees again, without you having to figure out or know where they came from. The static function will automatically find out whether the XML document was exported from the user or system Preferences and will automatically import them into the tree they were exported from.

Version ≥ Java SE 7

try (InputStream is = …) {
// This is a static call on the Preferences class
Preferences.importPreferences(is);
} catch (IOException ioe) {
// Exception whilst reading data from the InputStream
ioe.printStackTrace();
} catch (InvalidPreferencesFormatException ipfe) {
// Exception whilst parsing the XML document tree
ipfe.printStackTrace();
}

Version < Java SE 7

InputStream is = null;
try {
    is = …;
    // This is a static call on the Preferences class
    Preferences.importPreferences(is);
} catch (IOException ioe) {
    // Exception whilst reading data from the InputStream
    ioe.printStackTrace();
} catch (InvalidPreferencesFormatException ipfe) {
     // Exception whilst parsing the XML document tree
     ipfe.printStackTrace();
} finally {
     if (is != null) {
         try {
            is.close();
         } catch (IOException ignored) {}
    }
}

Removing event listeners

Event listeners can be removed again from any Properties node, but the instance of the listener has to be kept around for that.

Version ≥ Java SE 8

Preferences preferences = Preferences.userNodeForPackage(getClass());

PreferenceChangeListener listener = evt -> {
     System.out.println(evt.getKey() + " got new value " + evt.getNewValue());
};
preferences.addPreferenceChangeListener(listener);

//
// later…
//

preferences.removePreferenceChangeListener(listener);

Version < Java SE 8

Preferences preferences = Preferences.userNodeForPackage(getClass());
PreferenceChangeListener listener = new PreferenceChangeListener() {
     @Override
     public void preferenceChange(PreferenceChangeEvent evt) {
          System.out.println(evt.getKey() + " got new value " + evt.getNewValue());
     }
};
preferences.addPreferenceChangeListener(listener);

//
// later…
//

preferences.removePreferenceChangeListener(listener);

The same applies for NodeChangeListener.

Getting preferences values

A value of a Preferences node can be of the type String, boolean, byte[], double, float, int or long. All invocations must provide a default value, in case the specified value is not present in the Preferences node.

Related Article: Reference Types in Java

Preferences preferences = 
Preferences.userNodeForPackage(getClass());

String someString = preferences.get("someKey", "this is the default value");
boolean someBoolean = preferences.getBoolean("someKey", true);
byte[] someByteArray = preferences.getByteArray("someKey", new byte[0]);
double someDouble = preferences.getDouble("someKey", 887284.4d);
float someFloat = preferences.getFloat("someKey", 38723.3f);
int someInt = preferences.getInt("someKey", 13232);
long someLong = preferences.getLong("someKey", 2827637868234L);

Setting preferences values

To store a value into the Preferences node, one of the putXXX() methods is used. A value of a Preferences node can be of the type String, boolean, byte[], double, float, int or long.

Preferences preferences = Preferences.userNodeForPackage(getClass());

preferences.put("someKey", "some String value");
preferences.putBoolean("someKey", false);
preferences.putByteArray("someKey", new byte[0]);
preferences.putDouble("someKey", 187398123.4454d);
preferences.putFloat("someKey", 298321.445f);
preferences.putInt("someKey", 77637);
preferences.putLong("someKey", 2873984729834L);

Leave a Comment