Friday, June 20, 2014

Problem Creating Executable JAR

I'm trying to help a friend who doesn't know jar files. We need to make an executable jar file. Normally, I would use ANT to create a executable JAR but my friend doesn't know ANT either. I have a problem running the JAR because although I specify the classpath in the manifest, the Class-Path portion is omitted from the manifest included in the JAR.

For test purposes, I created the following Java class
package com.toysrus;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SuperNewApp {
 static Logger LOGGER = LoggerFactory.getLogger(SuperNewApp.class);
 
 public static void main(String[] args) {
  LOGGER.info( "starting SuperNewApp" );
  System.out.println("This is SuperNewApp");
 }
}
This is the manifest file I created from the instructions in Oracle's jar tutorial
Main-Class: com.toysrus.SuperNewApp
Class-Path: lib/slf4j-api-1.7.2.jar 
 lib/slf4j-jdk14-1.7.2.jar 

and I compile and jar the class with the following commands:
$ javac -cp lib/slf4j-api-1.7.2.jar -sourcepath src/java/com/toysrus -d bin src/java/com/toysrus/SuperNewApp.java
$ jar cvfm SuperNewApp.jar Manifest.txt lib/* -C bin/ . 
This is the manifest created in the jar (note it omits the Class-Path)
Manifest-Version: 1.0
Created-By: 1.8.0-ea (Oracle Corporation)
Main-Class: com.toysrus.SuperNewApp

Because the Class-Path attribute is omitted from the manifest, the application doesn't run because of a ClassNotFoundException.
$ java -jar SuperNewApp.jar
Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory
 at com.toysrus.SuperNewApp.(SuperNewApp.java:7)
Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory
 at java.net.URLClassLoader$1.run(URLClassLoader.java:359)
 at java.net.URLClassLoader$1.run(URLClassLoader.java:348)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:347)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
 ... 1 more
$ 
However, the Java does run in my development environment with the classpath fully specified as with the following command.
$ java -cp SuperNewApp.jar:lib/slf4j-api-1.7.2.jar:lib/slf4j-jdk14-1.7.2.jar com.toysrus.SuperNewApp
Jun 20, 2014 1:26:43 PM com.toysrus.SuperNewApp main
INFO: starting SuperNewApp
This is SuperNewApp
$
However, we do not want to include the long classpath in our deployed application and we want the SLF4J jar files to be packaged in our JAR file. For it to work in this manner, the classpath specified in the manifest must work.


Okay, I got it working. The manifest seems to require a trailing space before the newline after each library listed in the classpath. I originally had a carriage return and started each subsequent new line with a space but, in order for this to work, a trailing space is also required.
$ jar cvfm SuperNewApp.jar Manifest.txt lib/* -C bin/ . 
added manifest
adding: lib/slf4j-api-1.7.2.jar(in = 26083) (out= 22615)(deflated 13%)
adding: lib/slf4j-jdk14-1.7.2.jar(in = 7845) (out= 6465)(deflated 17%)
adding: .DS_Store(in = 6148) (out= 178)(deflated 97%)
adding: com/(in = 0) (out= 0)(stored 0%)
adding: com/toysrus/(in = 0) (out= 0)(stored 0%)
adding: com/toysrus/SuperNewApp.class(in = 723) (out= 448)(deflated 38%)
$ java -jar SuperNewApp.jar
Jun 20, 2014 1:46:26 PM com.toysrus.SuperNewApp main
INFO: starting SuperNewApp
This is SuperNewApp
$
Again, the manifest as shown initially works (i.e., CLASS-PATH included in the manifest) but, trailing spaces are required at the end of each line on the multi-line classpath.