Tuesday, November 8, 2011

Running a jar - how confusing this can be!

Let us try running the HelloWorld program as a jar - just to see how confusing this can ge.

Lets first pack our HelloWorld.class in a HelloWorld.jar
Go to the directory which contains the HelloWorld.class and give the following command
> jar cf HelloWorld.jar HelloWorld.class

The 'c' stands for create and 'f' stands for send the output to file, in this case, HelloWorld.jar

Next, try to run the jar using the following command - it will fail (see below):
> java -jar HelloWorld.jar

 C:\users\LavanniM\confucius>jar cf HelloWorld.jar HelloWorld.class  
   
 C:\users\LavanniM\confucius>java -jar HelloWorld.jar  
 Failed to load Main-Class manifest attribute from  
 HelloWorld.jar  
   
 C:\users\LavanniM\confucius>  


What happened?

When we initially ran the HelloWorld program from HelloWorld.class,the JRE 'knew' it had to look for the 'main' method in the HelloWorld.class
But when faced with a jar, which can potentially contain N classes, each of which might have a 'main', it cannot figure out which class's 'main' method to call.

Enter MANIFEST.MF

Inside each jar, is a directory called META-INF, and inside the META-INF is a file called MANIFEST.MF

This is a simple text file with name:value property pairs

Here is what it's contents look like:
 Manifest-Version: 1.0  
 Created-By: 1.6.0_21 (Sun Microsystems Inc.)  
   
   


JRE looks into this file for a property called Main-Class - if it does not find it, it throws the error we saw above.

So how do we put a MAIN-CLASS property in MANIFEST.MF? We can't simply edit this file because it will get recreated with the jar, and our changes will be lost!

Here is how to do this:

Create a new file called Manifest.txt in /confucius
Add a key-value property to it:

 Main-Class: HelloWorld  
   


Very Important: The line has to end with a newline - so make sure you hit enter!

Now recreate the HelloWorld.jar with the following command:
> jar cfm HelloWorld.jar Manifest.txt HelloWorld.class

The 'm' stands for include name-value pairs in MANIFEST.MF

Now try to run the new HelloWorld.jar - it will fail again (See below)!

 C:\users\LavanniM\confucius>jar cfm HelloWorld.jar Manifest.txt HelloWorld.class  
   
 C:\users\LavanniM\confucius>java -jar HelloWorld.jar  
 Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/BasicConfigurator  
     at HelloWorld.main(HelloWorld.java:7)  
 Caused by: java.lang.ClassNotFoundException: org.apache.log4j.BasicConfigurator  
     at java.net.URLClassLoader$1.run(Unknown Source)  
     at java.security.AccessController.doPrivileged(Native Method)  
     at java.net.URLClassLoader.findClass(Unknown Source)  
     at java.lang.ClassLoader.loadClass(Unknown Source)  
     at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)  
     at java.lang.ClassLoader.loadClass(Unknown Source)  
     ... 1 more  
   
 C:\users\LavanniM\confucius>  


What now? Why can't it find the log4j class?

Turns out, when you run jar, JRE does not look at the CLASSPATH environment variable. It looks for a Class-Path property in the MANIFEST-MF.

So let us add another property, Class-Path, to our Manifest.txt - like this:

 Main-Class: HelloWorld  
 Class-Path: C:\users\LavanniM\confucius\HelloWorld.jar C:\users\LavanniM\confucius\lib\log4j-1.2.16.jar  
   


Again, do not forget to hit Enter (to add newline) after the last line.
Also, note that we use space to separate the two paths, and there are no semi-colons.

Try recreating the jar and running it - it will fail again (see below):

 C:\users\LavanniM\confucius>jar cfm HelloWorld.jar Manifest.txt HelloWorld.class  
   
 C:\users\LavanniM\confucius>java -jar HelloWorld.jar  
 Exception in thread "main" java.lang.NoClassDefFoundError: HelloWorld  
 Caused by: java.lang.ClassNotFoundException: HelloWorld  
     at java.net.URLClassLoader$1.run(Unknown Source)  
     at java.security.AccessController.doPrivileged(Native Method)  
     at java.net.URLClassLoader.findClass(Unknown Source)  
     at java.lang.ClassLoader.loadClass(Unknown Source)  
     at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)  
     at java.lang.ClassLoader.loadClass(Unknown Source)  
 Could not find the main class: HelloWorld. Program will exit.  
   
 C:\users\LavanniM\confucius>  


This time the reason is that we gave absolute path for the Class-Path property, but JRE expects relative paths.

Update the Manifest.txt as follows:

 Main-Class: HelloWorld  
 Class-Path: HelloWorld.jar lib\log4j-1.2.16.jar  
   



Now if you recreate the jar and run it, it will run (phew!)

 C:\users\LavanniM\confucius>jar cfm HelloWorld.jar Manifest.txt HelloWorld.class  
   
 C:\users\LavanniM\confucius>java -jar HelloWorld.jar  
 0 [main] DEBUG HelloWorld - Hello World!  

1 comment:

Anonymous said...

You say "Turns out, when you run jar, JRE does not look at the CLASSPATH environment variable!!
It looks for a Class-Path property in the MANIFEST-MF!"

But let's say I have "n" jar's used by my project... and lets "m" out of those "n" jar's are using a specific Library (i.e. a jar library)... if their manifests are specifying different locations for the jar file that they are using, this will create a messed up situation for the user.

Would appreciate your thoughts.