Creating a Java-based service on Linux that will start when the operating system boots and run continuously doesn’t require any extra libraries, despite many internet posts to the contrary.
I wrote a web application using an embedded Jetty server that I needed to run as a service on Ubuntu 14.04.
In a previous project we needed to do the same thing on Windows and we used Java Service Wrapper (JSW), but then Tanuki Software changed their free license to GPL, which we could not use for a commercial application. We switched to Yet Another Java Service Wrapper (YAJSW), which has a more friendly license and uses the same configuration file format as JSW. This all worked fine, but in my latest project, I was deploying on Ubuntu 14.04, trying to build everything from Maven, and trying to keep everything as simple as possible. I found that the Appassembler Maven Plugin will generate a JSW configuration file, but there were too many moving parts for my taste.
Next I tried Apache Commons Daemon (jsvc), which was easy to integrate and worked fine with Maven, but I found the commandline very finicky. It took a long time to get the commandline arguments correct. I was attracted to jsvc because it was used by Tomcat, but then I found a post that said they removed it from Tomcat deployed on Ubuntu and Debian.
There is a much simplier way.
The Answer
First, I implemented a Java shutdown hook so when the process receives an interrupt, the service will exit cleanly. This feature is built into the JVM. The shutdown hook can be tested by running the application and then pressing Control-C in the console.
Second, I added a plugin to my pom.xml
to use Appassembler to produce a shell script that will
run my Java program. The plugin configuration looked something like this:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>appassembler-maven-plugin</artifactId>
<version>1.10</version>
<configuration>
<assembleDirectory>${project.build.directory}/assembly</assembleDirectory>
<!-- Generate bin scripts for windows and unix pr default -->
<repositoryName>lib</repositoryName>
<repositoryLayout>flat</repositoryLayout>
<platforms>
<platform>unix</platform>
</platforms>
<configurationDirectory>config</configurationDirectory>
<configurationSourceDirectory>configs/production</configurationSourceDirectory>
<copyConfigurationDirectory>true</copyConfigurationDirectory>
<programs>
<program>
<mainClass>com.webappsthatwork.WebappMain</mainClass>
<id>web-server</id>
</program>
</programs>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>assemble</goal>
</goals>
</execution>
</executions>
</plugin>
The Appassembler plugin is nice because it automatically handles:
- copying all dependent libraries
- creating a shell script with the appropriate classpath
- passing system properties for the application’s home directory
Now I had a simple shell script to run which starts the web application.
Third, I wrote an Upstart configuration file. My service was called web
, so the file is called
web.conf:
description "Web Server"
start on runlevel [2345]
stop on runlevel [!2345]
respawn
respawn limit 10 5
setuid web
setgid web
umask 027
chdir /opt/web
console none
# Modify these options as needed
env JAVA_OPTS=""
exec authbind --deep /opt/web/bin/web-server
The file above does the following:
- Restarts the server if it dies, but if it restarts more than 10 times in 5 seconds, the system will not try to restart it again.
- The owner and group of the process are both set to
web
. This avoids running the process as root. - The process umask is 027, which means any log files created will be writable by user web, readable by group web, and have no access by anybody else.
- The process runs out of the directory
/opt/web
- All console output is redirected to
/dev/null
- The environment has the variable
JAVA_OPTS
added to it, which the generated shell script uses for additional JVM arguments.
For more details, there is good documentation of the Upstart stanzas.
Also, notice that the service itself is launched using authbind. This allows the service to
run as user web
, but have permission to bind to ports 80
and 443
.
All I did was copy the web.conf
file to /etc/init
and then run
# service web start
Conclusion
I found this method very straightforward. If I find later that I’m missing something and JSW, YAJSW, or jsvc add something I was missing, I’ll update this post.