1. Introduction

Every Maven project includes a packaging property that specifies the artifact type like jar, war, and others. The default packaging type is jar and generally, we don't explicitly specify it in the pom file. When we build a Maven project, the resulting jar file contains only the project classes and resources excluding the dependencies. This is perfectly fine if our project serves as a library and others use it as a dependency. However, if we want to run our Maven project as an application, then we must provide the dependencies to the application in some way. In this tutorial, we're going to look at how we can create an executable jar with dependencies using Maven.

2. Sample Application

Let's start with the sample application.

Our Maven project defines two dependencies in pom.xml:

<dependencies>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.10</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-io</artifactId>
        <version>1.3.2</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

Note that we define commons-io as a provided dependency.

Then we have the HelloWorld class that acts as the main class:

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println(StringUtils.upperCase("Hello World"));
    }
}

3. Maven Assembly Plugin

The Maven Assembly Plugin allows us to create an executable jar containing the project output and the related dependencies. To use this plugin, we must first have an assembly descriptor file which determines the archive type and contents. Luckily, the Maven Assembly Plugin also provides some predefined descriptor files for common scenarios. After selecting or creating the required descriptor, we'll integrate the plugin to our project:

<build>
    <finalName>application</finalName>
    <plugins>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>com.javabyexamples.java.jar.HelloWorld</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>

In the configuration section, we're first specifying a prefabricated descriptor file, jar-with-dependencies, in a descriptorRef element. As the name implies, it tells the plugin to create a jar file including the project dependencies. Secondly, to create an executable jar, we're specifying the main class in an archive element. This enables us to set the Main-Class entry in the MANIFEST.MF file:

Manifest-Version: 1.0
...
Main-Class: com.javabyexamples.java.jar.HelloWorld

After configuring the plugin we must decide on how to run the plugin. In this example, we're binding the assembly:single goal to execute in the package phase. So to create the assembly, just start a build targeting the package phase or above:

mvn clean package

This build produces two artifacts: the original jar and the new jar with dependencies - application.jar and application-jar-with-dependencies.jar.

Alternatively, run the assembly:single goal explicitly in the command line:

mvn clean compile assembly:single

With this command, we're creating the assembly after compiling the source code. So we get a single jar containing all project dependencies, application-jar-with-dependencies.jar.

Now that we have our executable jar, we'll run it using the java command:

java -jar application-jar-with-dependencies.jar

3.1. Jar Contents

We'll now look at how the Maven Assembly Plugin collects and archives dependencies into a single jar file. First of all, the final jar doesn't contain other jars but only the classes and resources. To include a dependency, it extracts the class files preserving the package structure and aggregates with others.

Remember that our project declares two dependencies. The resulting jar contains:

/com/javabyexamples/java/jar -> HelloWorld.class
/org/apache/commons/lang3 -> commons-lang3 classes and other package directories

Here, we have both the project classes and dependency classes.

One important point is that the jar-with-dependencies descriptor doesn't collect dependencies with the provided scope. For example, we don't have the commons-io classes in the resulting jar.

4. Maven Shade Plugin

There can be overlapping files when aggregating the contents of the dependencies. Unfortunately, the Maven Assembly Plugin doesn't provide efficient tools to handle these cases. As a better alternative, the Maven Shade Plugin provides resource transformers to handle the overlaps and support class relocation.

Now, we'll configure the plugin to create an executable uber jar:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.2.2</version>
    <configuration>
        <transformers>
            <transformer
                implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                <mainClass>com.javabyexamples.java.jar.HelloWorld</mainClass>
            </transformer>
        </transformers>
        <shadedArtifactAttached>true</shadedArtifactAttached>
        <shadedArtifactId>application</shadedArtifactId>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
        </execution>
    </executions>
</plugin>

In the configuration section, we're setting the main class using ManifestResourceTransformer. Then we're setting the shadedArtifact property as true. This way, the shaded jar is attached to the original jar. In the executions section, we're binding the shade:shade goal to the package phase.

Now that we have the configuration in place, just start a build targeting package or above:

mvn clean package

Alternatively, run the plugin goal explicitly:

mvn clean package shade:shade

These commands produce two artifacts. The original artifact is application.jar and the attached artifact is the uber jar, application-1.0-SNAPSHOT-shaded.jar.

4.1. Jar Contents

The Maven Shade Plugin employs a similar approach to the Maven Assembly Plugin. The uber jar aggregates all classes and resources from all dependencies into a signal archive. Again it doesn't copy the dependencies with the provided scope.

5. Spring-Boot Maven Plugin

The Spring-Boot Maven Plugin offers a different approach to create an uber jar. Mainly, it provides its own main class and organizes the jar contents using a specific layout.

Take a look at the plugin configuration:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <classifier>spring-boot</classifier>
        <mainClass>com.javabyexamples.java.jar.HelloWorld</mainClass>
    </configuration>
    <executions>
        <execution>
            <id>repackage</id>
            <phase>package</phase>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

In the configuration section, we're first defining the artifact classifier as spring-boot. Without a classifier, the Spring-Boot Maven Plugin replaces the original artifact. This way it attaches the repackaged uber jar with the given classifier. Then we're setting the main class to make the repackaged jar executable. Finally, in the executions section, we're binding the spring-boot:repackage goal to the package phase.

After integrating the plıugin to our project, creating an uber jar is easy. Just start a build targeting package or above:

mvn clean package

In the end, we get two jars. application.jar is the original jar whereas application-spring-boot.jar is the repackaged one containing all dependencies.

5.1. Jar Contents

As we mentioned previously, the Spring-Boot Maven Plugin takes a different approach when organizing the jar contents. A repackaged jar contains the application’s classes and dependencies in BOOT-INF/classes and BOOT-INF/lib respectively. Moreover, it doesn't extract the dependencies and stores them as jar files.

This layout decision is in tandem with the execution policy. The Spring-Boot Maven Plugin provides its own launcher class to bootstrap the application. So it changes the manifest entries to support this behavior. Take a look at the contents of the MANIFEST.MF file:

Manifest-Version: 1.0
...
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.javabyexamples.java.jar.HelloWorld
Spring-Boot-Version: 2.1.7.RELEASE
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/

It sets Main-Class as org.springframework.boot.loader.JarLauncher keeping the original main class in the Start-Class entry.

This layout structure has another important implication. The repackaged jar can't be used as a dependency in other Maven projects. The dependent project can't load the project classes since they're under the BOOT-INF/classes directory.

6. Summary

In this tutorial, we've looked at creating an executable jar with dependencies. The resulting jar, also known as an uber jar, can run as a standalone application. For this purpose, we first investigated the Maven Assembly Plugin. Then we learned that the Maven Shade Plugin provides more support to handle the overlapping resources. Lastly, we checked the Spring-Boot Maven Plugin to create an uber jar embedding dependencies as jar files.

As always the source code for all examples is available on Github.