Wrapper Script for Java Command-Line Applications
Java applications with a command-line interface (CLI) are typically started with a shell script wrapper. This wrapper sets up the environment for the Java application, specifies the JVM that is going to be used, and finally starts the application.
Recently, I learned the hard way, that writing a shell script wrapper for a Java application is not trivial. My application, denoted as myapp in this example, uses the following common directory layout:
myapp/
bin/
myapp
lib/
vendor.jar
ch/
plesslweb/
myapp/
CLI.class
other.class
...
The wrapper used for starting the application is located in the bin/ directory. The lib/ directory contains all libraries that are used by myapp as JAR archives. When running the application, these libraries need to be added to the classpath. The root for the application's class files is the installation directory, i.e., the classes live in ch/plesslweb/myapp/....
Without a wrapper script, the application will be started with a command like the following (assuming that myapp is installed in directory /opt/myapp):
java -cp /opt/myapp -cp /opt/myapp/lib/vendor.jar ch.plesslweb.myapp.CLI arg1 arg2 arg3
Note, that above command uses absolute paths to specify the classpath. Relative paths can be used only, if the current working directory when running the command is known in advance.
This problem is solved with a wrapper, that allows for starting the application either by calling the wrapper 'myapp' in the bin/ directory directly, or by calling the wrapper indirectly, i.e., by executing a symbolic link to 'myapp'. Using a symbolic link to the application is convenient, if the user cannot or does not want to install the application in a system directory. Instead, myapp can be installed anywhere in the filesystem and a symbolic link to the wrapper is placed in a directory that is included in the user's PATH. Many popular Java applications such as Maven and Ant use this approach.
The following shell script can serve as a boilerplate for your own Java application wrapper. The script contains code fragments that I have extracted from Maven's wrapper (mvn). The wrapper serves two main functions: a) it allows for setting up variables used in the wrapper by providing system and user-specific configurations, and b) it determines the installation path of the application:
In this example, the user has 3 ways of specifying a non-standard name for the Java interpreter: either in a system-wide configuration file (/etc/myapprc), in a user-specific configuration file (~/.myapprc) or using the JAVACMD environment variable. The configuration files are sourced in the wrapper, i.e., JAVACMD can be specified by adding a line like "JAVACMD=/opt/bin/myjava" to the configuration file. If JAVACMD is not set explicitly, the wrapper uses 'java' as default.
The installation path of the application is determined and stored in MYAPP_HOME. First, if the wrapper is executed via a symbolic link, the link is resolved. If the link points to another symbolic link, this procedure is repeated until the final non-link target of the link is reached. In a second step, the resulting path is converted to an absolute path (MYAPP_HOME). This variable can now be used for conveniently specifying paths relative to the installation directory. For example, the library directory can be specified as $MYAPP_HOME/lib.
#!/bin/sh
if [ -f /etc/myapprc ] ; then
. /etc/myapprc
fi
if [ -f "$HOME/.myapprc" ] ; then
. "$HOME/.myapprc"
fi
if [ -z "$JAVACMD" ] ; then
# use JAVACMD defined in environment variable or in myapprc
else
JAVACMD=java
fi
## resolve links - $0 may be a link to application
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
# make it fully qualified
saveddir=`pwd`
MYAPP_HOME=`dirname "$PRG"`/..
MYAPP_HOME=`cd "$MYAPP_HOME" && pwd`
cd "$saveddir"
echo "myapp is installed in $MYAPP_HOME"
MAINCLASS=ch.plesslweb.myapp.Runner
EXEC="$JAVACMD -cp $MYAPP_HOME -cp $MYAPP_HOME/lib/vendor.jar $MAINCLASS $@"
echo $EXEC
exec $EXEC
Edit: 29 Jan 07 -- fix style and typos in text.
Written January 28th, 2007