Class-Path Wildcards in Mustang
2006/02/15 10:17:43 -08:00

Most non-trivial Java programs depend upon external libraries in the form of jar files. Tools like Ant make it easier to cope with large collections of jar files at build time, but at runtime one often has to resort to passing a class path to the VM which explicitly names each and every jar file needed by the application. Such class paths are typically baked into a launch script or .BAT file, like so:

#! /bin/bash
LIB=$(dirname $0)/../lib
CP=$LIB/foo.jar:$LIB/bar.jar:$LIB/baz.jar
java -classpath $CP com.xyzzy.app.Main

This is fragile from a maintenance standpoint, of course, since you have to remember to update the script every time you add or remove a jar file.

A more flexible solution can be had at the cost of writing a more complex launcher that carefully figures out where all of the jar files for your program reside and constructs the required classpath. This can pretty quickly get out of hand, however, as witnessed by, e.g., the NetBeans 5.0 launch script for Unix or—worse—the equivalent C++ code for Windows.

Mustang addresses this annoying problem with the introduction of class-path wildcards. The basic idea is very simple: Instead of listing individual jar files in a directory, as in the example above, you can instead just use an asterisk to stand for all of the jar files that can be found in that directory:

java -classpath $LIB/'*' com.xyzzy.app.Main

Note that you must quote the asterisk in order to prevent it from being interpreted by your shell and expanded into something that’s guaranteed not to work if there’s more than one jar file in the directory. When designing this feature we considered various other syntaxes that wouldn’t require quoting. In the end, however, we decided to go with the asterisk since it’s already familiar to Ant users and we figured that anyone clever enough to use class-path wildcards would also be clever enough to understand shell quotation.

How do they work? Expansion of wildcards is done early, prior to the invocation of a program’s main method, rather than late, during the class-loading process itself. This simplifies both the semantics and the implementation and is also the most compatible approach. Each element of the input class path containing a wildcard is replaced by the (possibly empty) sequence of elements generated by enumerating the jar files in the named directory. If the directory foo contains a.jar, b.jar, and c.jar, e.g., then the class path foo/* is expanded into foo/a.jar:foo/b.jar:foo/c.jar, and that string will be the value of the system property java.class.path.

The order in which the jar files in a directory are enumerated in the expanded class path is not specified and may vary from platform to platform and even from moment to moment on the same machine. A well-constructed application shouldn’t depend upon any particular order; if a specific order is required then the jar files must be enumerated explicitly in the class path.

Subdirectories are not searched recursively, i.e., foo/* only looks for jar files in foo, not in foo/bar, foo/baz, etc. There’s no equivalent to Ant’s /** construct, though that could be added later on.

A wildcard only matches jar files, not class files that happen to be in the same directory. If you want to load both class files and jar files from a single directory foo then you can say foo:foo/*, or foo/*:foo if you want the jar files to take precedence.

Class-path wildcards work in the -classpath (equiv. -cp) command-line option to the JVM launcher and most other command-line tools, and also in the CLASSPATH environment variable. They don’t work in in the Class-Path header of a jar-file manifest, nor do they work in the bootstrap class path (but you’d never use that anyway, right?).

Useful, but non-standard Class-path wildcards are an implementation-specific feature. They’re supported in Sun’s Mustang implementations, and they may be available in other implementations. As a feature of command-line tools, however, they’re not part of the Platform Specification, so there’s nothing that requires every implementation of Java SE 6 to support them.