JAR files, the CLASSPATH, and the main-class Specification
Author: Davis Swan
Date: June 2, 2004
I have read several articles that cover these topics, but I still felt that a concise summary of the issues was missing. Hopefully, the following document clarifies the use of jar files, particularly those with packages and those including a main-class. It is assumed that the reader is familiar with compiling Java files and may have already created jar files.
Introduction
The fundamental concept that must be understood is that a CLASSPATH is not a search directory, but an explicit list of classes that will be used by the class loader. In a very simple case where you have a single class file in the current directory, you might think that you dont need a CLASSPATH at all. Consider the following example.
/**
* The HelloWorld class implements an application that
* simply displays "Hello World!" to the standard output.
*/
class HelloWorld {
public static void main(String[] args) {
// Display the string
System.out.println("Hello World!");
}
}
Once you have compiled this source file to create the class
file HelloWorld.class in the current directory, you might expect that you could
type java HelloWorld
and the
program would execute. It will not. Instead, you will get the ever-popular
NoClassDefFoundError.
That is because the class loader will prefix the class HelloWorld with NULL and add the extension .class to arrive at the resulting fully qualified class HelloWorld.class
. It will then attempt to open that file as an absolute path using the OS and will not find it, because there is no assumed default path to the current directory.
Typing the command SET CLASSPATH=.
(or, in Linux/Unix SET CLASSPATH .
) changes the behaviour of the class loader. It will now prefix the class name with .\ (or ./ in Linux/Unix) and add the extension to get the fully qualified class name .\HelloWorld.class (or ./HelloWorld.class in Linux/Unix) and the OS will be able to find the file when you type java HelloWorld
.
The explicit nature of the CLASSPATH explains why the following simple procedure also fails. Assume you create a jar file with the command:
jar cvf HelloWorld.jar HelloWorld.class
You then delete the HelloWorld.class file as being
redundant. Now, even with your
CLASSPATH set to . (the current working directory), the command java HelloWorld
fails. That is because the class loader will still be trying to find the
exact file .\HelloWorld.class (./HelloWorld.class in Linux/Unix) which no
longer exists. The class loader will
NOT do a search of any existing jar file unless it is explicitly told to do
so. You can force a search of a particular
jar file by explicitly including the file in the CLASSPATH.
For example, if you now use the command SET CLASSPATH=HelloWorld.jar
(SET CLASSPATH HelloWorld.jar
in
Linux/Unix), the class loader will scan all the entries in the jar file. It will add the .class extension to any classes referred to by the Java application, and will look for exactly that entry in the jar file.
Note: If you used the command jar cvf HelloWorld.jar .\*.class
(jar cvf HelloWorld.jar ./*.class
in Linux/Unix) you would end up with exactly the same jar file because the assumed base path and explicitly named path are identical. You can prove that to yourself by typing jar tvf HelloWorld.jar
to list the contents of the jar
file.
Moving Class Files to a Subdirectory
Lets now separate the location of the class file from the directory where the jar file is located.
Create a new directory classDir (pay attention to the case), recompile HelloWorld.java and move the HelloWorld.class file to classDir.
Create a jar file using the command jar cvf HelloWorld.jar classDir\*.class
(jar cvf HelloWorld.jar classDir/*.class
in Linux/Unix).
Delete the file HelloWorld.class from classDir since it is now redundant.
You have now created a situation where there is no possible way to get HelloWorld to execute from any directory.
The command java
HelloWorld
will cause the class loader to add the current directory and
extension to create the fully qualified class file .\HelloWorld.class (./HelloWorld.class in Linux/Unix), which no
longer exists.
Typing the command java
classDir/HelloWorld
will also fail, which may seem strange. Doing a jar
tvf HelloWorld.jar
will produce a listing which includes an entry for
classDir/HelloWorld.class, which is exactly what you are trying to execute.
The problem here is that the class loader does not view the class as classDir/HelloWorld, but only as HelloWorld. Therefore the class loader fails because there is a discrepancy between the physical contents of the jar file (classDir/HelloWorld) and the logical name of the class (HelloWorld). This is because the simple act of moving the class file to a subdirectory has created an implicit Java package.
There is a lot of documentation on package creation available at java.sun.com. Packages are used to uniquely identify classes which otherwise might be in conflict. The most confusing thing about packages is the automatic relationship that exists between class file directories and package names.
As already mentioned, by moving the file HelloWorld.class to a subdirectory an implicit Java package was created. Therefore, the source code file HelloWorld.java must be changed by adding the package declaration in order to make it execute.
/**
* The HelloWorld class implements an application that
* simply displays "Hello World!" to the standard output.
*/
package classDir;
class HelloWorld {
public static void main(String[] args) {
// Display the string
System.out.println("Hello World!");
}
}
We can now recompile the java file, move the resultant HelloWorld.class file to the classDir subdirectory, create the jar file with the command jar cvf HelloWorld.jar classDir\*.class (jar cvf HelloWorld.jar classDir/*.class in Linux/Unix), and delete the HelloWorld.class file from the classDir subdirectory.
With the CLASSPATH still set to HelloWorld.jar, the command java classDir/HelloWorld will now work properly. This is because the jar file entry (which did not change at all when the package declaration was added) now matches the logical name of the class.
Note that if the package declaration inside HelloWorld.java was package classdir
the command java HelloWorld
would fail because the package declaration and the directory tree are both case
sensitive, even though the Windows Command prompt is not. Note also that the creating of the implicit
package depends upon your location in the directory tree when you issue the jar
command. In the example above, if you
moved to the classDir subdirectory, and typed jar
cvf HelloWorld.jar *.class
, then the jar file would be created as per
the original configuration.
The bottom line is this. If you are having trouble with a jar file, use the command jar tvf someJarFile.jar
to list the contents of the jar file. If there are subdirectories in the listing you get you have to make sure that the package declarations in the source files exactly match these entries, and that the correct command is used to execute the class.
A More Complex Example
The following example lists a more complex directory structure for HelloWorld.class together with the package and execution commands.
If the directory structure is com\myOrg\myApplication\ (com/myOrg/myApplication in Linux/Unix) then the jar file must be created from the parent directory of this structure, using the following command:
jar cvf HelloWorld.jar com\myOrg\myApplication\*.class
Windows
jar cvf
HelloWorld.jar com/myOrg/myApplication/*.class
Linux/Unix
The following package declaration must be included in all
the java source files: package com.myOrg.myApplication
.
Assuming that the CLASSPATH is set to HelloWorld.jar, the application will be executed using the command java com/myOrg/myApplication/HelloWorld
Avoiding CLASSPATH Problems by Using a MANIFEST
There is a way to completely avoid all of these CLASSPATH
issues if you are dealing with a single jar file. The command java jar HelloWorld.jar
will always work, even with no CLASSPATH set at all, provided that the jar file contains a MANIFEST with an entry for the main-class. The MANIFEST entry must match the logical class name. Therefore, in the original example discussed previously, the file MANIFEST.MF would have the following lines.
Manifest-Version: 1.0
Created-By: 1.3.1_08 (Sun Microsystems Inc.)
Main-Class: classDir/HelloWorld
Assuming that this file is placed in the classDir subdirectory, the correct command to create the jar file would be;
jar cvfm HelloWorld.jar
classDir\MANIFEST.MF classDir\*.class
Windows
jar cvfm
HelloWorld.jar classDir/MANIFEST.MF classDir/*.class
Linux/Unix
The command java
jar HelloWorld.jar
will now work from any location, regardless of the
current setting for CLASSPATH.
For the more complicated example described above the MANIFEST.MF would have the following entries.
Manifest-Version:1.0
Created-By: 1.3.1_08 (Sun Microsystems Inc.)
Main-Class: com/myOrg/myApplication/HelloWorld
Adding References to External Files in the MANIFEST
In some cases a Java application needs access to external files such as JDBC driver files or user preference files. It is worth noting that these external files can be referenced explicitly in the jar file, so that the application will work properly without any setting for the CLASSPATH.
Assume that the application makes a connection to an Oracle database, requiring the JDBC driver classes12.zip, and reads user preferences from a file named user.pref. The correct MANIFEST entry would be as follows;
Class-Path: . ./classes12.zip
As long as the files classes12.zip and user.pref are copied to the directory where the jar file exists then the application will work perfectly without having to use an explicit CLASSPATH. That is, a command such as java -jar myApp.jar is all that is required.
Within the Java application, the following code could be used to read the file user.pref.
InputStream userPrefStream;
BufferedReader userPrefReader;
String userPrefLine;
userPrefStream = ClassLoader.getSystemResourceAsStream("user.pref");
if ( userPrefStream != (InputStream)null )
{
userPrefReader = new BufferedReader( new InputStreamReader(userPrefStream));
try
{
while ((userPrefLine = userPrefReader.readLine()) != null )
{
System.out.println(userPrefLine);
// More processing code.
}
}
}