Digested from Java Tip 127: See JAR run
The manifest file and the Main-Class entry
Inside most JARs, a file called MANIFEST.MF is stored in a directory called META-INF. Inside that file, a special entry called Main-Class tells the java -jar command which class to execute.
The problem is that you must properly add this special entry to the manifest file yourself—it must go in a certain place and must have a certain format. However, some of us don't like editing configuration files.
Let the API do it for you
Since Java 1.2, a package called java.util.jar has let you work with jar files. (Note: It builds on the java.util.zip package.) Specifically, the jar package lets you easily manipulate that special manifest file via the Manifest class.
Let's write a program that uses this API. First, this program must know about three things:
1. The JAR we wish to make runnable
2. The main class we wish to execute (this class must exist inside the JAR)
3. The name of a new JAR for our output, because we shouldn't simply overwrite files
Write the program
The above list will constitute our program's arguments. At this point, let's choose a suitable name for this application. How does MakeJarRunnable sound?
Check the arguments to main
Assume our main entry point is a standard main(String[]) method. We should first check the program arguments here:
if (args.length != 3) {
System.out.println("Usage: MakeJarRunnable "+ "<jar file> <Main-Class><output>");
System.exit(0);
}
Please pay attention to how the argument list is interpreted, as it is important for the following code. The argument order and contents are not set in stone; however, remember to modify the other code appropriately if you change them.
Access the JAR and its manifest file
First, we must create some objects that know about JAR and manifest files:
//Create the JarInputStream object, and get its manifest
JarInputStream jarIn = new JarInputStream(new FileInputStream(args[0]));
Manifest manifest = jarIn.getManifest();
if (manifest == null) {
//This will happen if no manifest exists
manifest = new Manifest();
}
Set the Main-Class attribute
We put the Main-Class entry in the manifest file's main attributes section. Once we obtain this attribute set from the manifest object, we can set the appropriate main class. However, what if a Main-Class attribute already exists in the original JAR? This program simply prints a warning and exits. Perhaps we could add a command-line argument that tells the program to use the new value instead of the pre-existing one:
Attributes a = manifest.getMainAttributes();
String oldMainClass = a.putValue("Main-Class", args[1]);
//If an old value exists, tell the user and exit
if (oldMainClass != null) {
System.out.println("Warning: old Main-Class value is: "
+ oldMainClass);
System.exit(1);
}
Output the new JAR
We need to create a new jar file, so we must use the JarOutputStream class. Note: We must ensure we don't use the same file for output as we do for input. Alternatively, perhaps the program should consider the case where the two jar files are the same and prompt the user if he wishes to overwrite the original. However, I reserve this as an exercise for the reader. On with the code!
System.out.println("Writing to " + args[2] + "...");
JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(args[2]),manifest);
We must write every entry from the input JAR to the output JAR, so iterate over the entries:
//Create a read buffer to transfer data from the input
byte[] buf = new byte[4096];
//Iterate the entries
JarEntry entry;
while ((entry = jarIn.getNextJarEntry()) != null) {
//Exclude the manifest file from the old JAR
if ("META-INF/MANIFEST.MF".equals(entry.getName())) continue;
//Write the entry to the output JAR
jarOut.putNextEntry(entry);
int read;
while ((read = jarIn.read(buf)) != -1) {
jarOut.write(buf, 0, read);
}
jarOut.closeEntry();
}
//Flush and close all the streams
jarOut.flush();
jarOut.close();
jarIn.close();
Complete program
Of course, we must place this code inside a main method, inside a class, and with a suitable set of import statements. The Resources section provides the complete program.
Usage example
Let's put this program to use with an example. Suppose you have an application whose main entry point is in a class called HelloRunnableWorld. (This is the full class name.) Also assume that you've created a JAR called myjar.jar, containing the entire application. Run MakeJarRunnable on this jar file like so:
java MakeJarRunnable myjar.jar HelloRunnableWorld myjar_r.jar
Again, as mentioned earlier, notice how I order the argument list. If you forget the order, just run this program with no arguments and it will respond with a usage message.
Try to run the java -jar command on myjar.jar and then on myjar_r.jar. Note the difference! After you've done that, explore the manifest files (META-INF/MANIFEST.MF) in each JAR. (You can find both JARs in the source code.)
Here's a suggestion: Try to make the MakeJarRunnable program into a runnable JAR!
Run with it
Running a JAR by double-clicking it or using a simple command is always more convenient than having to include it in your classpath and running a specific main class. To help you do this, the JAR specification provides a Main-Class attribute for the JAR's manifest file. The program I present here lets you utilize Java's JAR API to easily manipulate this attribute and make your JARs runnable.
source code and JARs for this tip