Creating Executable JARs with Dependencies in Maven

Maven is a powerful build automation tool for Java projects. A common requirement during project delivery is to package your application and all its dependencies into a single, executable JAR file. This allows for easy distribution and execution without requiring users to manage dependencies themselves. This tutorial will guide you through the process of creating such a JAR using Maven.

Understanding the Goal

The objective is to create a JAR file that includes your application’s compiled code and all the external libraries (dependencies) that your application relies on. This “fat JAR” or “uber JAR” contains everything needed to run your application, simplifying deployment.

Using the Maven Assembly Plugin

The most common and recommended way to create executable JARs with dependencies is by utilizing the Maven Assembly Plugin. This plugin provides a flexible mechanism to assemble project artifacts, including dependencies, into a single archive.

Step 1: Add the Maven Assembly Plugin to your pom.xml

First, add the maven-assembly-plugin to the <build> section of your pom.xml file:

<build>
  <plugins>
    <plugin>
      <artifactId>maven-assembly-plugin</artifactId>
      <version>3.3.0</version> <!-- Use the latest version -->
      <configuration>
        <archive>
          <manifest>
            <mainClass>fully.qualified.MainClass</mainClass>
          </manifest>
        </archive>
        <descriptorRefs>
          <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
      </configuration>
    </plugin>
  </plugins>
</build>
  • <artifactId>maven-assembly-plugin</artifactId>: Specifies the plugin to use.
  • <version>: Pin the plugin version to ensure consistent builds.
  • <archive>: Configures the archive format (JAR in this case) and allows you to specify a manifest file.
  • <manifest>: Defines the manifest file for the JAR.
    • <mainClass>: Crucially, set this to the fully qualified name of your application’s main class (the class with the main method). This tells the Java runtime where to start execution.
  • <descriptorRefs>: Specifies a pre-defined assembly descriptor. jar-with-dependencies is a standard descriptor that includes all your project’s dependencies in the resulting JAR.

Step 2: Build the Assembly

Once the plugin is configured, you can build the assembly using the following Maven command:

mvn clean compile assembly:single
  • mvn clean: Deletes any previously built artifacts.
  • compile: Compiles your Java code. This is necessary before creating the assembly.
  • assembly:single: Invokes the assembly plugin to create a single JAR file containing your application and its dependencies. The resulting JAR will be located in your project’s target directory.

Step 3: Automating Assembly Creation with Build Phases

To automatically create the assembly with every build (e.g., during mvn install or mvn deploy), you can bind the assembly plugin to a specific build phase. Here’s how:

<build>
  <plugins>
    <plugin>
      <artifactId>maven-assembly-plugin</artifactId>
      <version>3.3.0</version>
      <configuration>
        <archive>
          <manifest>
            <mainClass>fully.qualified.MainClass</mainClass>
          </manifest>
        </archive>
        <descriptorRefs>
          <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
      </configuration>
      <executions>
        <execution>
          <id>make-assembly</id>
          <phase>package</phase>
          <goals>
            <goal>single</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

By adding the <executions> section, the assembly:single goal will be executed during the package phase, automatically creating the assembly each time you run mvn package, mvn install, or mvn deploy.

Alternative Approach: Copying Dependencies and Configuring the Manifest

Another, less common approach involves manually copying dependencies to a separate directory and configuring the JAR manifest to include them in the classpath.

Step 1: Copy Dependencies with maven-dependency-plugin

<build>
  <plugins>
    <plugin>
      <artifactId>maven-dependency-plugin</artifactId>
      <executions>
        <execution>
          <id>copy-dependencies</id>
          <phase>prepare-package</phase>
          <goals>
            <goal>copy-dependencies</goal>
          </goals>
          <configuration>
            <outputDirectory>${project.build.directory}/lib</outputDirectory>
            <overWriteReleases>false</overWriteReleases>
            <overWriteSnapshots>false</overWriteSnapshots>
            <overWriteIfNewer>true</overWriteIfNewer>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

This configuration copies all dependencies to a lib directory within the project’s build output.

Step 2: Configure the JAR Manifest

<plugin>
  <artifactId>maven-jar-plugin</artifactId>
  <configuration>
    <archive>
      <manifest>
        <addClasspath>true</addClasspath>
        <classpathPrefix>lib/</classpathPrefix>
        <mainClass>theMainClass</mainClass>
      </manifest>
    </archive>
  </configuration>
</plugin>
  • <addClasspath>true</addClasspath>: Tells the JAR plugin to add the dependencies to the classpath.
  • <classpathPrefix>lib/</classpathPrefix>: Specifies the directory where the dependencies are located relative to the JAR file.

This approach can be more complex to manage, but it offers finer control over how dependencies are packaged.

Best Practices

  • Specify the Main Class: Always explicitly specify the mainClass in the manifest file. This ensures that the JAR file is executable.
  • Use the Latest Plugin Versions: Keep your Maven plugins up to date to benefit from bug fixes and performance improvements.
  • Automate Assembly Creation: Integrate the assembly plugin into your build process to ensure that the assembly is always created with each build.
  • Consider Shade Plugin: For more complex scenarios involving conflicting dependencies, consider using the Maven Shade Plugin, which can repackage dependencies to avoid conflicts.

Leave a Reply

Your email address will not be published. Required fields are marked *