Friday, March 9, 2012

Solution for the "Issue with Executable/Runnable Jar file"

I was facing an issue when running an Executable Jar file.:

I had a simple Java Project, in which I had a class named "Sample.java". I need to use log4j for logging statements. This project should be archived as an Executable Jar.

My Project has the following files:











The Sample.java looks like:
package com.dwij.sample;

import org.apache.log4j.Logger;

public class Sample {

 public static final Logger LOGGER = Logger.getLogger(Sample.class);

 public static void main(String[] args) {
  LOGGER.info("Simple usage of log4j");
 }
}


You can download the zip file of this project from here

In the Manifest.MF file, mention your Main class and classpath:
Manifest-Version: 1.0
Class-Path: lib/log4j.jar
Main-Class: com.dwij.sample.Sample



Now I go to command prompt and issue commands for:
  1. Compile sources: javac -sourcepath src -classpath classes;lib\log4j.jar src\com\dwij\sample\*.java -d classes
  2. Archive the project into a Jar file: jar -cvfm Sample.jar Manifest.MF -C lib . -C classes . .classpath .project
  3. Now run the jar file: java -jar Sample.jar. (Issue this command after placing the Sample.jar in a separate folder)
    • This fails with an error: NoClassDefFoundError for the org.apache.log4j.Logger class
Why is so?
That is because, When you have your main class inside a Jar file and if you need to refer to classes from the Jar files from this main class, which are again packaged inside the same Jar, YOU JUST CAN'T DO IT.
There is a thread which explains this issue: Executable JAR ClassPath problem

Solution 1:
You will be able to run this only when the Sample.jar is sitting beside a folder named "lib", which has the log4j.jar.

That is because, when you run the Executable Jar, the Manifest.MF is read for the classpath property. In our case, we mentioned lib\log4j.jar. So it looks for a "lib" folder at the same level where the jar resides.
But this is not an optimal solution, ideally, we would like to package our code in a single self-contained Jar file.

Solution2:
Here, the requirement is to package all the dependent jars also inside the Sample.jar.
For this, I used Eclipse's solution. When you export a Java Project from Eclipse, and mark it as Runnable Jar specifying your Main class, all the dependent Jar files will be packaged into the resultant Runnable Jar file and there are 5 classes that are added by Eclipse. Apart from these classes, Eclipse modifies the Manifest.MF file to add the following parameters:

Rsrc-Class-Path: ./ log4j.jar
Class-Path: .
Rsrc-Main-Class: com.dwij.sample.Sample
Main-Class: org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader

Here, Eclipse is using the concept of URL Class loaders. This manifest file will be read and your actual Main class will be called after pointing the classpath to the Jars files mentioned in the "Rsrc-Class-Path" property.

I included these classes in my source project, compiled and archived.

That works like a charm!

Here are the steps:
  • Copy the following classes into your source project:
Now go to command prompt and issue commands for:
  • Compile sources: javac -sourcepath src -classpath classes;lib\log4j.jar src\com\dwij\sample\*.java src\org\eclipse\jdt\internal\jarinjarloader\*.java -d classes
    • Observe that we are compiling the new package as well.
  • Archive the project into a Jar file: jar -cvfm Sample.jar Manifest.MF -C lib . -C classes . .classpath .project
  • Now run the jar file: java -jar Sample.jar. (Issue this command after placing the Sample.jar in a separate folder)
  • Observe that the Jar runs fine! 

Happy executing-your-Runnable-Jar-files! :)

1 comment: