October 26, 2012

Java Preferences API - An Introduction



In this Java Tutorial we are going to discuss about a somewhat old but not so famous API of the Java Programing Language. It has been a long time since the Java Preferences API has been introduced to Java SE (JDK). Java Preferences API is extremely lightweight and a cross platform persistent API. Being a persistent API, it does not deal with the database engines but uses OS specific backend to store and retrieve data. 


Many a times our programs are required to store/access smaller amount of data, something like user preferences, or system preferences. The preferences data is so small that it is too expensive to use something like a Database driven persistence layer or any other registry services. Making use of the properties file along with the Java Properties API could have been a better solution, but the problem with the properties files is that they don’t have any standards about where the files should be stored on a disk and what should be the naming strategy for multiple properties files. This makes it difficult to use properties API as a standard and cross platform 


In JDK 1.4, java.util.prefs package was introduced, which has a simple API for storing the preferences to the OS specific backend. The Java Preferences API doesn’t talk about how the API should be implemented. The different implementations of JRE can implement the API specific to its targeted OS. On the operating systems like Windows the preferences are stored on the OS level registries, and for non-Windows environments, they can be stored into other registry like storages, may be on a simple XML file as well.





How To:


The API is designed to work with almost all of the Java Basic data types like numbers, Booleans, characters and strings. The data is stored in a form of key-value pairs, which makes it very simple to use. As per the usual programming needs the API specifies to have two different nodes/storages of preferences one for User Preferences and other for System Preferences. Below lines show how simple it is to get access to these preferences. Though the node storages and the ways to access it are different on different environments, the below code works everywhere. 

Preferences userPreferences = Preferences.userRoot();
Preferences systemPreferences = Preferences.systemRoot();



In below example we will try to put an integer to the user preferences and on the next line retrieve it with a get method. The get method takes an extra parameter that specifies a default value, which will be returned if the key is not found within the user node or the node is not accessible. 

//Store an int to User Preferences with String key
userPreferences.putInt("NUMBER_OF_ROWS", 25);
  
//Retrive an entry from User Preferences, 
//the number sent as a second parameter will be returned if the key doesnt exist
int numberOfRows = userPreferences.getInt("NUMBER_OF_ROWS", 10);



The API also provides us with a way to remove any preference: shown below. 

userPreferences.remove("NUMBER_OF_ROWS ");

The remove method doesn’t throw any exception even if doesn’t find the given preference in a node.




Package Specific Preference Roots:  


As the preferences are stored directly on the OS level storages, they do not have any dependency on the program or the JRE accessing it. A preference set by a program can be accessed by another program, which is running under a totally different JRE on the same machine. This leads to a problem as different programs to store different values can use the same ‘key’. When each program calls put for a same key, the preference value is actually being overwritten. 

To overcome this problem, the Java Preferences API has come up with different subsets of the preference roots. The preferences stored within a specific sub root will only be accessible within the sub root. Different sub roots can now have their own preferences with the same key. The Java Preferences API supports package level sub roots and any class within the package can access the same set of preferences. 

Preferences userPreferences = Preferences.userNodeForPackage(getClass());  
userPreferences.put("LANGUAGE_I_SPEAK", "ENGLISH");

In this example the preference is created as “User Preference Node: /com” where the ‘/com’ is the package of my class (getClass()). Now below is a code from another class of the same package.


Preferences userPrefsFromAnotherClass = Preferences.userNodeForPackage(getClass()); 
String language = userPreferences.get("LANGUAGE_I_SPEAK", "JAVA");



The package specific preferences are created as a separate sub-root. But the sub-roots are not aware of name of the project containing the package. Hence, if two different projects have packages with same name, the preferences create by one package will be accessible to the other package of different project. We need to take care in this scenario.




Custom Preference Nodes:


In the above example the sub-roots are created specific to the package names. But we can also create our own sub-roots with custom logical root names. In the below example I am creating a user preference with a logical root name. The same preferences can be accessed from any program and package on the same machine. 

Preferences userPreferences = Preferences.userRoot().node("/my/custom/root");
userPreferences.put("LANGUAGE_I_SPEAK", "ENGLISH");


Now the above preference can also be accessed by another program.


Preferences userPrefsFromAnotherClass = Preferences.userRoot().node("/my/custom/root"); 
String language = userPrefsFromAnotherClass.get("LANGUAGE_I_SPEAK", "JAVA");




We have seen the examples of user preferences but the system preferences works exactly the same way. In any application we can have a dedicated class to deal with the system preferences, and the other parts of the programs will access the preferences through the static methods of the class. But this is not that safe in case of user preferences, as there could be multiples uses logged into the system with different preferences.