download one-jar examples one-jar at sourceforge.net simontuffs.com licensing information SourceForge.net Logo

Deliver Your Java Application in One-JAR!

by P. Simon Tuffs, Software Architect and Consultant (www.simontuffs.com)


Introduction (or Quickstart)

Delivering a runnable Java application is an exercise often frought with complexity and frustation. In principle, if you have a main class called com.mydomain.mypackage.Main, you can simply say: However, this assumes that some magic has been applied to your shell environment to set the value of your CLASSPATH so that the java runtime can actually find this class. The CLASSPATH also has to contain all of the other classes which will be loaded and executed by your main program.

This is where things start to get much more complicated. Multiple entries on the CLASSPATH must be separated by a special character, and this special character is unfortunately operating system dependent (since it would otherwise interact badly with the common command shells). For example on UNIX it is ':', on DOS it is ';'. If these choices were reversed, the command shell on each operating system would become very confused. And there had better not be version any conflicts between entries on the CLASSPATH: the order of entries controls the order of class resolution, incompatible versions created by incorrect orderings will lead to obscure NoSuchMethodException exceptions and other runtime problems.

Wouldn't it be nice if you could just wrap up all your classes into one big executable, and run it? Well the Java2 Runtime Environment has a way of doing this using a JAR launcher. If you bundle your class, and all the supporting classes in a file called myapp.jar, and set the MANIFEST.MF appropriately, you can simply say:

Very nice. So what's the catch? There are numerous. One catch is that you must first unjar all of the supporting classes and flatten them out into a single directory tree before creating the final myapp.jar. For example: what happens if your application depends on some of the web-services JAR files from the Java Web Services Developer Pack? There are many of them and they all contain code which lives in the same packages (javax.*, com.sun.* etc).

Is this a problem? Well it may be. Consider that after you have expanded all of the code into a single tree, you have lost all trace of any code signatures that might have been present. Why? Because code signatures are located in /META-INF/MANIFEST.MF file, and each jar file will contain one of these manifest files, with different content. Likewise, any resources which have the same name (a distinct possibility given that the jar files themselves may contain flattened out packages) will be in conflict: you can only have one file called log4j.properties to control logging, where you might like to enable/disable logging on a boundary set by the individual jar files.

Wouldn't it be nice if you could just bundle the supporting jar-files into your myapp.jar file without expanding them? This is the problem addressed, and solved, by One-JAR.

Class-Path Manifest Does Not Work (like you might think!)

Judging by the conversations on Usenet which we have seen, many people have tried and failed to use the META-INF/MANIFEST.MF property called Class-Path to solve this problem.

But first, lets set up a concrete example of what we're trying to do. Suppose our main class, plus other classes we have written is in a JAR file called main.jar Suppose that our application depends on two other JAR files, called a.jar, b.jar Can we construct a manifest which will allow the JAR launcher to run this? First, we construct our compound Jar file:

The manifest contains a Main-Class setting to launch our application, and entries for the supporting JAR files a.jar and b.jar. What happens if you run this? Looks good, exactly what we want right? Wrong. What's happening here is that the Class-Path entries are being resolved in the file-system, not inside the JAR file. If you move this JAR file somewhere else and try to run it, you get the following: This is most unfortunate, and most frustrating, and pretty much unexpected by developers who follow this route.

What about using the 'jar:' protocol in the Class-Path entry? This doesn't work either. It appears that the JAR loader only supports file-based URLS. Which is where One-JAR comes in.

One-JAR: Cracking the Problem

At this stage we have laid out a rational format for our application JAR file. This layout is in the spirit of the layout specified by J2EE containers for Web-Application and Enterprise Application code, and there is a reason for this as we will see.

QuickStart

All that is required to enable an application for launching with One-JAR is to create a bootstrap environment which is capable of installing a classloader that understands the One-JAR layout, and which can locate and start the main program.

If you're really impatient to see One-JAR in action without writing any code or jarring any jars, follow this link.

At this point we have to augment our myapp.jar file with some bootstrap code, and a META-INF/MANIFEST.MF file to let the bootstrap code assume control.

One-JAR ships with a pre-built bootstrap jar file called one-jar-boot.jar that contains all the code you need to do this. You can download the latest pre-built one-jar-boot.jar here:

There are five classes which control the bootstrap process, all residing in the com.simontuffs.onejar package. We'll be discovering the details of these later.

There is also a pre-built manifest file called boot-manifest.mf. To complete the One-JAR deployment process, create a suitable directory and expand this file into it, then update the myapp.jar file as follows:

Thats it! You can now run your application in the simplest possible fashion:

But How Do I Know It's Working?

This looks like the previous working case, how do you know that things are actually going to work when you move this JAR file to a new location and run it?

Well, One-JAR has some built-in diagnostics that you can enable to see what it is doing on behalf of your application. These can be enabled using command-line switches to the JVM, placed before the -jar option:

Let's look at the program output when we enable each of these diagnostic levels: Now we see that something interesting is indeed happening inside the application: One-JAR is using a custom classloader called JarClassLoader to load information from the myapp.jar file. We can get more detail: Notice how the JarClassLoader keeps track of where classes came from: for example the com.a.A class is contained inside the lib/a.jar file inside myapp.jar.

At this stage you should be asking "How does One-JAR decide which is the main class?". The conventional way would be to have a manifest attribute in the top-level JAR file myapp.jar and require this to be edited before the final jar was assembled. But this is all unnecessary: One-JAR simply looks for a jar file inside the main sub-directory of the composite JAR file myapp.jar, and provided that that JAR file has a Main-Class manifest attribute, it will be used as the main entry point. This allows an existing main-class JAR file to be bundled inside a One-JAR archive without further modification!

That's a high-level overview of One-JAR, next we'll get into the details behind the custom classloader JarClassLoader and what it does to make all of this possible.

Super-Quick Start One-JAR Example

If you're really in a hurry to see One-JAR in action, there is a pre-built One-JAR example which can be downloaded here:

Once you save this file to disk you can run it as shown below. Note that this example is fairly complex (it's really a regression test for the One-JAR product and should be broken out into JUnit tests). We'll be discussing precisely what is going on in the next section.

You can gain more insight into the role of the One-JAR classloader by running with a java -Done-jar.verbose command. Don't be perturbed by the failure, it is expected and caused by pathalogical behaviour in one of the test cases.


If you like One-JAR then you might want to check out some of the other Open-Source projects developed by simontuffs.com:
soap-stone at sourceforge.net XML Instance Generator Eclipse JAR Plugin Yet Another Compiler Compiler Language