Wednesday, September 14, 2011

Common scenarios for Java application packaging

It all started from an overgrown answer to a question on StackOverflow. I realized that I haven't seen an overview of best practices and patterns for packaging of Java applications. There are the platform specific packaging formats, but they have more to deal with installation, rather than the layout of the packaged application. Below are a few common scenarios I've come across over the last few years.

Note: I've written another, somewhat related article on SO, about common strategies for managing configuration files in Java

A single uberjar with MAIN-CLASS specified in the manifest is convenient distribution format for simple tool applications that are intended to be run on the command line. You can copy it around and plop it somewhere on the path or in your home directory.  If you set the PATHEXT and file associations on Windows, you can run them as if they were native executables. One thing to watch out for is when you bundle libraries with custom entries in the manifest (you might need to manually merge them), when multiple jar's contain files with the same paths but different content (i.e. Spring namespace handlers, ServiceLoader implementations, LDAP SPI providers, etc.)

If for some reason you are bundling a library and all dependencies as a single jar (bad idea), you better take care to relocate the packages (for example, see how the JDK has relocated Xerces and BCEL under com.sun.org...) Not doing so can cause subtle problems to your clients which are extremely annoying, to say the least (*caugh*Weblogic*caugh*). On the other hand, relocating classes causes problem with smart usages of reflection relying on string concatenation. Also, you will most likely need to filter all XML and properties files, replacing the packages with the relocated names. All files under META-INF/services will need to be renamed too. All in all, bundling dependencies in a library jar causes far more problems than it solves.

When your application has more than a few options, you may find you want a config file. One approach I like is to package a template config in my app and look in a predefined location for override (i.e. in ~/.myapp) - you may provide instructions how to extract the template config and place it under the users home or you can have the app do it automatically on first usage. Which one you choose depends on your estimate how often an user will need to tweak the configs (we don't want to pollute the user home unnecessarily). A single directory app is usually installed in your user home directory.

If your application has dependencies on external services or special environment, you will need a startup script (shell or batch file) - in this case, the simplest deployment layout is single directory with uberjar and startup script. As you now have a directory, it is a good idea to add a README file and you may as well put the configs there. There are a number of variations on this theme. Usually, you want to add this directory to the app classpath as well, so any file there may be looked up as resource - that provides convenient way to override things like Spring descriptors, patch single classes, etc. These are usually installed in your home, /usr/local, or sometimes under opt.

If your application bundles many dependencies, has complex configuration, startup scripts for multiple platforms etc. then you will go with full blown deployment structure along these lines (used for big application deployed on multiple platforms):
  • /bin - startup scripts, rc3.d scripts, environment setup files, registry entries, etc.
  • /config - configuration files for app, should be possible to relocate to /etc by specifying env variable.
  • /logs - logs produced by app, safe to delete, config should allow us to move it to /var
  • /cache - temporary data produced by app, safe to delete, config should allow us to move it to /var
  • /data - data produced by application, config should allow us to move it to /etc
  • /lib - main application jar with all dependencies 
  • /lib/win86 - platform specific JNI libs for all supported platforms, selected by the start script, passed as -Djava.library.path=...
  • /lib/linux86
  • /lib/linux64
  • /lib/...
The main application JAR should have a Main-Class attribute starting the app and Class-Path attribute specifying the ordering of the dependencies (all located in the same dir). The Class-Path should start with the ../config path that will be used to load configurations and patch classes. This allows to keep the startup scripts simple.

The last layout allows you to run the application from your home, work all from /opt, or install in read-only /opt and put the configs and writable dirs under /etc and /var like a decent Unix application. The last option also allows you to run a few instances with separate configs and data from the same binaries.

No comments:

About Me: check my blogger profile for details.

About You: you've been tracked by Google Analytics and Google Feed Burner and Statcounter. If you feel this violates your privacy, feel free to disable your JavaScript for this domain.

Creative Commons License This work is licensed under a Creative Commons Attribution 3.0 Unported License.