Lives in the UK. Likes blogging, cycling and eating lemon drizzle cake. Roger is a DZone MVB and is not an employee of DZone and has posted 143 posts at DZone. You can read more from them at their website. View Full User Profile

Tracking Exceptions - Part 6 - Building an Executable Jar

05.09.2014
| 7740 views |
  • submit to reddit

If you’ve read the previous five blogs in this series, you’ll know that I’ve been building a Spring application that runs periodically to check a whole bunch of error logs for exceptions and then email you the results. 

Having written the code and the tests, and being fairly certain it’ll work the next and final step is to package the whole thing up and deploy it to a production machine. The actual deployment and packaging methods will depend upon your own organisation's processes and procedures. In this example, however, I’m going to choose the simplest way possible to create and deploy an executable JAR file. The first step was completed several weeks ago, and that’s defining our output as a JAR file in the Maven POM file, which, as you’ll probably already know, is done using the packaging element:

     <packaging>jar</packaging>


It’s okay having a JAR file, but in this case there’s a further step involved: making it executable. To make a JAR file executable you need to add a MANIFEST.MF file and place it in a directory called META-INF. The manifest file is a file that describes the JAR file to both the JVM and human readers.

As usual, there are a couple of ways of doing this, for example if you wanted to make life difficult for yourself, you could hand-craft your own file and place it in the META-INF directory inside the project’s src/main/resources directory. On the other hand, you could use themaven-jar plug-in and do it automatically. To do that, you need to to add the following to your POM file.

       <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.4</version>
            <configuration>
                 <archive>
                     <manifest>
                           <addClasspath>true</addClasspath>
                           <mainClass>com.captaindebug.errortrack.Main</mainClass>
                           <classpathPrefix>lib/</classpathPrefix>
                      </manifest>
                 </archive>
            </configuration>
       </plugin>


The interesting point here is the <archive><manifest> configuration element. It contains three sub-elements: 

  1. addClasspath: this means that the plug-in will add the classpath to the MANIFEST.MF file so that the JVM can find all the support jars when running the app.
  2. mainClass: this tells the plug-in to add a Main-Class attribute to the MANIFEST.MF file, so that the JVM knows where to find the the entry point to the application. In this case it’s com.captaindebug.errortrack.Main
  3. classpathPrefix: this is really useful. It allows you to locate all the support jars in a different directory to the main part of the application. In this case I’ve chosen the very simple and short name of lib.


If you run the build and then open up the resulting JAR file and extract and examine the /META-INF/MANIFEST.MFfile, you’ll find something rather like this:

Manifest-Version: 1.0

Built-By: Roger
Build-Jdk: 1.7.0_09
Class-Path: lib/spring-context-3.2.7.RELEASE.jar lib/spring-aop-3.2.7.RELEASE.jar lib/aopalliance-1.0.jar lib/spring-beans-3.2.7.RELEASE.jar lib/spring-core-3.2.7.RELEASE.jar lib/spring-expression-3.2.7.RELEASE.jar lib/slf4j-api-1.6.6.jar lib/slf4j-log4j12-1.6.6.jar lib/log4j-1.2.16.jar lib/guava-13.0.1.jar lib/commons-lang3-3.1.jar lib/commons-logging-1.1.3.jar lib/spring-context-support-3.2.7.RELEASE.jar lib/spring-tx-3.2.7.RELEASE.jar lib/quartz-1.8.6.jar lib/mail-1.4.jar lib/activation-1.1.jar
Created-By: Apache Maven 3.0.4
Main-Class: com.captaindebug.errortrack.Main
Archiver-Version: Plexus Archiver


The last step is to marshall all the support jars into one directory, in this case the lib directory, so that the JVM can find them when you run the application. Again, there are two ways of approaching this: the easy way and the hard way. The hard way involves manually collecting together all the JAR files as defined by the POM (both direct and transient dependencies) and copying them to an output directory. The easy way involves getting the maven-dependency-plugin to do it for you. This involves adding the following to your POM file:

       <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <version>2.5.1</version>
            <executions>
                 <execution>
                      <id>copy-dependencies</id>
                      <phase>package</phase>
                      <goals>
                          <goal>copy-dependencies</goal>
                      </goals>
                      <configuration>
                           <outputDirectory>
                                ${project.build.directory}/lib/
                           </outputDirectory>
                      </configuration>
                 </execution>
            </executions>
       </plugin>


In this case you’re using the copy-dependencies goal executed in the package phase to copy all the project dependencies to the${project.build.directory}/lib/ directory - note that the final part of the directory path, lib, matches theclasspathPrefix setting from the previous step.

In order to make life easier, I’ve also created a small run script: runme.sh:

#!/bin/bash         

echo Running Error Tracking...
java -jar error-track-1.0-SNAPSHOT.jar com.captaindebug.errortrack.Main


And that’s about it. The application is just about complete. I’ve copied it to my build machine where it now monitors the Captain Debug Github sample apps and build. 

I could, and indeed may, add a few more features to the app. There are a few rough edges that need knocking off the code: for example is it best to run it as a separate app, or would it be a better idea to turn it into a web app? Furthermore, wouldn’t it be a good idea to ensure that the same errors aren’t reported twice?

I may get around to thart soon... or maybe I'll talk about something else; so much to blog about so little time...

The code for this blog is available on Github at: https://github.com/roghughe/captaindebug/tree/master/error-track. If you want to look at other blogs in this series take a look here… 

  1. Tracking Application Exceptions With Spring
  2. Tracking Exceptions With Spring - Part 2 - Delegate Pattern
  3. Error Tracking Reports - Part 3 - Strategy and Package Private
  4. Tracking Exceptions - Part 4 - Spring's Mail Sender
  5. Tracking Exceptions - Part 5 - Scheduling With Spring
Published at DZone with permission of Roger Hughes, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Gilbert Herschberger replied on Fri, 2014/05/09 - 3:32pm

Your article has been helpful to me because I'm just getting around to build an executable JAR for my project again. It reminds me that the Java class path doesn't always need to be passed to the java command. But of course, we're talking about an application that doesn't support plug-ins.

It might be more helpful to see a diagram of the installed application. Is your executable JAR file self-contained? Or, do you need to copy a separate "lib" directory with additional JARs? At first, I assumed that dependent JARs were embedded in the executable JAR because I've been working with OSGi for so long; but, I'm not sure that works with the Class-Path: manifest property.

By the way, in your runme.sh shell script, isn't the com.captaindebug.errortrack.Main parameter redundant? The name of the main class is embedded in the JAR using the Main-Class: manifest property so you really don't need it again on the command line. Maybe. (I notice that your main method doesn't process command line arguments.)

Raging Infernoz replied on Wed, 2014/05/14 - 4:08pm

Yes the first plugin is useful, and I use it with similar config; however I mostly prefer using the shade plugin because I don't want the hassle (including system security policies) of having to deploying a lib folder or the alternatives, with ugly jar-in-jar classloader hacks or iffy at-runtime deployment.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.