Sunday, November 20, 2011

Classpath and Resource Files in Java Programs

(Last updated on February 2, 2013)

Frequently a Java program needs to read some resource files in the file system. Such a resource file may be a .properties file or a .xml file for program configuration. Often it is not practical to hardcode the full path to such a file in the program because if we do so, we will not be able to execute the program correctly except from a specific location in the file system. That is highly undesirable. 

A popular practice is to place such a resource file on a classpath and code the program to search the classpath for the resource file. A programmer who adopts this practice must understand well what a classpath is and how to discover it programmatically.

To understand classpath, one must at first understand the concept of class loader. According to the Java API document, “A class loader is an object that is responsible for loading classes”. In general, a Java program uses multiple class loaders, instead of a single one, to load classes. A classpath is the search path of a class loader. In other words, a Java program usually has multiple classpaths. (It is inappropriate to talk about the classpath of a Java program because a Java program has multiple classpaths. On the other hand, it is all right to talk about the classpath of a class loader.) The fact is that some of those classpaths can be discovered programmatically, some simply cannot.

For a typically Java program, the class loaders form a hierarchy.  When a class loader is requested to find a class or a resource file, it will at first recursively delegate the request to its parent class loader. Only when its parent class loader cannot find the class or resource file, it will search it on its own search path.


Class Loader Hierarchy



On the top of the class loader hierarchy is the JVM’s built-in bootstrap class loader, which loads standard JDK classes. The search path of the bootstrap class loader can be found programmatically via a call to System.getProperty("sun.boot.class.path"). The search path is platform specific. On a Windows machine, it is <JAVA-HOME>/jre/lib.  Typical jar files on this search path are rt.jar, jsse.jar, jce.jar etc.

As the child of the bootstrap class loader is the extension class loader, which loads JDK extension classes. The search path of the extension class loader can be found programmatically via a call to System.getProperty("java.ext.dirs").  The path is platform specific. On a Windows machine, it is <JAVA-HOME>/jre/lib/ext.  An example of such JDK extension is sunjce_provider.jar. 

As the child of the extension class loader is the system class loader, which loads classes on the path specified by OS environment variable CLASSPATH or the –classpath option to the JVM. The search path of the system class loader can be found programmatically via a call to System.getProperty("java.class.path").
If the Java program does not create its own user class loader, all non-JDK-standard/extension classes will be loaded by the system class loader. As we just said, the search path of this class loader can be easily found programmatically.

If the Java program creates its own user class loaders, unless the class loaders are of a custom class loader class with a method to retrieve the search path, there is no way to find the search path programmatically. If the user classes are executed in a JEE container, the JEE container is the bootstrap program and it always creates user class loaders to load user classes (usually one for each war or ear). Similarly, Maven always load plugin classes with user class loaders. Therefore,  if the user classes are executed as a Maven plugin, don’t expect to find the search path for those classes by calling System.getProperty("java.class.path"). It is worth to notice that many applications and application servers actually use instances of the java.net.URLClassLoader as their user class loaders. In such cases, the classpath of the class loaders can be found by call the getURLs() method on the classloader instances (after casting them from java.lang.ClassLoader to java.net.URLClassLoader). The getURLs() method returns an array or URLs. Each URL returned is a directory on the classpath. In other words, the class loader will search those directories to find the classes wanted.

No matter the search path can be found programmatically or not, a program can always asks a class loader to find a resource file by calling the getResource(String name) method on the classloader object (or to get an InputStream connected to the resource file by calling the getResourceAsStream(String name) method on the classloader object). It will found the resource file if it is on the search path or in a jar file on the search path. By the way, the getSystemResource(String name) and getSystemResourceAsStream(String name) methods are to find the resource on the search path of the system class loader.

By the way, given any object, one can call the getClass() method on it to find the Class object representing its class. Then one can find the class loader by calling the getClassLoader() method on the Class object. In short, obj.getClass().getClassLoader() will return the class loader that loaded the class of obj, an object.

It is usually preferable to use the getResource(String name) method rather than the getResourceAsStream(String name) method for the reason of logging. The returned URL can be logged. In case there are inadvertently multiple resource files with the same name on different locations on the search path, the logged URL can help to debug. Even if there is only one resource file with the name, if there is some difficult to read from or write to it, the logged URL can point the programmer quickly to the file that needs a fix.

1 comment:

Java tutorial said...

Great information. clear much of doubt related to ClassLoader. Indeed ClassPath is one of the most confusing for beginners and intermediate a like and deep knowledge of Classpath is must to solve NoClassDefFoundError and ClassNotFoundException in Java. I also like How Classpath works in Java