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! :)

Thursday, March 8, 2012

Using Log4J

In this post, I will try to keep things as simple as possible and explain how to use log4j in Java.

1. Create a Java Project in Eclipse, say "Sample"
2. Create a java class with main() method, say Sample.java.
3. Download log4j.jar. I would recommend you to download the latest jar.
4. Have that log4j.jar in buildpath of the project.
5. In the Sample.java, write the following code to include Logging statements:
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");
 }
}

6. Run the Sample.java class. Yes, the error you see is expected.
log4j:WARN No appenders could be found for logger (com.dwij.sample.Sample).
log4j:WARN Please initialize the log4j system properly.

7. How to fix this "No appenders issue"?
  • Have a log4j.properties in classpath of the project:
    • Create a file with name "log4j.properties" and have the below content in it:
      • log4j.rootCategory=DEBUG, S
        log4j.appender.S = org.apache.log4j.ConsoleAppender
        log4j.appender.S.layout = org.apache.log4j.PatternLayout
        log4j.appender.S.layout.ConversionPattern = %c{1} [%p] %m%n
    • The logging level can be DEBUG, INFO, ERROR, FATAL
    • "S" in log4j.rootCategory can be any string you want; it is just that you need to give the same string in the other properties (log4j.appender.S, log4j.appender.S.layout, log4j.appender.S.layout.ConversionPattern)
    • I am using ConsoleAppender here because I just want to see the Log statements in the console.
    • ConversionPattern goes something like this:
      • %c{1}  prints the classname that is ran
      • [%p] prints the Log level
      • %m prints the message provided in the log statement in the code
      • %n prints a new line
  • Define your own appender and add it to the Logger instance:
package com.dwij.sample;

import org.apache.log4j.Appender;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;

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

 public static void main(String[] args) {
  // Define Appender     
  myAppender = new ConsoleAppender(new SimpleLayout());  
  
  LOGGER.addAppender(myAppender);  
  LOGGER.setLevel(Level.ALL); 
 
  LOGGER.info("Simple usage of log4j");
 }
}

8. Run the class, and you should see the below output:
INFO - Simple usage of log4j

In order to keep things simple, I have not included examples which include writing Log statements to Files, etc.

I will add more content, if requested.