Saturday, January 11, 2014

Building an executable JAR with maven

Making a JAR executable

One common thing I come across is building executable JARs in maven, i.e. JARs that you can run as:
java -jar myapp.jar
The following maven snippet makes the result JAR executable - that is com.myapp.Main (or your main class of choice) is executed when the jar is run by java as above. It does so by setting the main class in your JAR manifest. (You can learn more about main classes and manifests here)


   <build>
      <plugins>
         ...
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
               <archive>
                  <manifest>
                     <mainClass>com.myapp.Main</mainClass>
                  </manifest>
               </archive>
            </configuration>
         </plugin>
         ...
      </plugins>
   </build>

However, if your JAR has any dependencies, you may get class not found errors because java cannot find the dependencies. One way is to pull in those dependencies by specifying a class path, e.g.:
java -cp "mydependency1.jar;mydependency2.jar;lib/*" -jar myapp.jar
either specifying each jar or the wildcards (if using Java 6 or higher).

Distributing with a subdirectory of dependencies

Another way is to get your JAR to put those dependencies in a subdirectory when building the JAR, and reference them in the manifest. This is more convenient if you are packaging it to run on another machine or in a installer.
   <build>
      <plugins>
         ...
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
               <archive>
                  <manifest>
                     <addClasspath>true</addClasspath>
                     <mainClass>com.myapp.Main</mainClass>
                     <classpathPrefix>lib/</classpathPrefix>
                  </manifest>
               </archive>
            </configuration>
         </plugin>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <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>
         ...
      </plugins>
   </build>
This does two things -- get maven-dependency-plugin to copy the dependencies (and transitive dependencies) at package time into a lib subdirectory, and then tell maven-jar-plugin to add the classpath entries when writing the manifest for your JAR.


Distributing as a single JAR (containing all dependencies)


The above method is great, but sometimes you want to have everything in a single JAR. You can use maven-assembly-plugin instead to build a single jar containing everything.

   <build>
      <plugins>
         ...
         <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
               <descriptorRefs>
                  <descriptorRef>jar-with-dependencies</descriptorRef>
               </descriptorRefs>
               <archive>
                  <manifest>
                     <mainClass>com.myapp.Main</mainClass>
                  </manifest>
               </archive>
            </configuration>
            <executions>
               <execution>
                  <id>make-my-jar-with-dependencies</id>
                  <phase>package</phase>
                  <goals>
                     <goal>single</goal>
                  </goals>
               </execution>
            </executions>
         </plugin>
         ...
      </plugins>
   </build>

This will, in addition to the normal output jar, make another jar e.g. "myapp-jar-with-dependencies.jar" that contains all the classes and resources from the dependencies. You can run this directly:
java -jar myapp-jar-with-dependencies.jar
Note here that we are using maven-assembly-plugin to specify the main class instead of maven-jar-plugin as well.

This method does not always work. It comes with a warning that some libraries may not run properly if processed like that - I have not come across a case so far but recommend it only for small apps.


No comments: