ZetCode

Maven Shade Plugin

last modified November 13, 2026

In this article we build uber JARs with the Maven Shade Plugin. We package an application with its dependencies into a single executable JAR, merge the service files that would otherwise overwrite each other, relocate the packages of a dependency, and remove unused classes. The examples use Maven 3.10, Shade 3.6.2, and Java 25.

An uber JAR, also called a fat JAR, contains the classes and resources of the application and of all its runtime dependencies. It runs with java -jar without any classpath setup, which makes it convenient for command line tools, serverless functions, and containers. The Shade plugin unpacks all JARs and packs their content into one. The Maven executable JAR article compares it with the other ways of building a runnable application.

The project

Our application greets a user in several languages. It is a multi-module project: the api module defines a service interface, the english and slovak modules implement it, and the app module finds the implementations with ServiceLoader.

greeter/api/src/main/java/com/example/api/Greeter.java
package com.example.api;

public interface Greeter {

    String language();

    String greet(String name);
}

The interface is a service that the other modules provide.

greeter/english/src/main/java/com/example/english/EnglishGreeter.java
package com.example.english;

import com.example.api.Greeter;

public class EnglishGreeter implements Greeter {

    @Override
    public String language() {
        return "English";
    }

    @Override
    public String greet(String name) {
        return "Hello, " + name + "!";
    }
}

The slovak module contains the same class with a Slovak greeting.

greeter/english/src/main/resources/META-INF/services/com.example.api.Greeter
com.example.english.EnglishGreeter

A provider is registered in a file in META-INF/services named after the service interface. Each line contains the name of an implementation class. The slovak module has a file with the same name, which lists com.example.slovak.SlovakGreeter.

greeter/app/src/main/java/com/example/app/Main.java
package com.example.app;

import com.example.api.Greeter;
import org.apache.commons.text.WordUtils;

import java.util.ServiceLoader;

public class Main {

    public static void main(String[] args) {
        String name = WordUtils.capitalizeFully(args.length > 0 ? args[0] : "world");
        for (Greeter greeter : ServiceLoader.load(Greeter.class)) {
            System.out.printf("%-8s %s%n", greeter.language(), greeter.greet(name));
        }
    }
}

The application capitalizes the name with Commons Text and greets it with every greeter that ServiceLoader finds on the classpath.

A basic uber JAR

The shade goal of the plugin is bound to the package phase.

greeter/app/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example</groupId>
        <artifactId>greeter</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>app</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>english</artifactId>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>slovak</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-text</artifactId>
            <version>1.15.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.6.2</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.example.app.Main</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

The application depends on both greeters and on Commons Text, which brings Commons Lang.

<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
    <mainClass>com.example.app.Main</mainClass>
</transformer>

Transformers process resources while the JARs are merged. The ManifestResourceTransformer sets the Main-Class attribute in the manifest, so the JAR can be run with java -jar.

Shade replaces the JAR created by the JAR plugin with the uber JAR and keeps the original one as original-app-1.0.0.jar. It warns about files that exist in more than one JAR, because only the first copy gets into the uber JAR. The application runs, but it found only the English greeter: the service file of the slovak module was dropped. The same thing happens in real projects with JDBC drivers, logging backends, and other libraries that register implementations in service files.

$ mvn package
--- shade:3.6.2:shade (default) @ app ---
Dependency-reduced POM written at: /home/user/project/greeter/app/dependency-reduced-pom.xml
english-1.0.0.jar, slovak-1.0.0.jar define 1 overlapping resource: 
  - META-INF/services/com.example.api.Greeter
commons-lang3-3.20.0.jar, commons-text-1.15.0.jar define 3 overlapping classes and resources: 
  - META-INF.versions.9.module-info
  - META-INF/LICENSE.txt
  - META-INF/NOTICE.txt
api-1.0.0.jar, app-1.0.0.jar, commons-lang3-3.20.0.jar, commons-text-1.15.0.jar, english-1.0.0.jar, slovak-1.0.0.jar define 1 overlapping resource: 
  - META-INF/MANIFEST.MF
maven-shade-plugin has detected that some files are
present in two or more JARs. When this happens, only one
single version of the file is copied to the uber jar.
Usually this is not harmful and you can skip these warnings,
otherwise try to manually exclude artifacts based on
mvn dependency:tree -Ddetail=true and the above output.
See https://maven.apache.org/plugins/maven-shade-plugin/
Replacing original artifact with shaded artifact.
BUILD SUCCESS
$ wc -c app/target/*.jar
978300 app/target/app-1.0.0.jar
  2745 app/target/original-app-1.0.0.jar
981045 total
$ java -jar app/target/app-1.0.0.jar "lucy smith"
English  Hello, Lucy Smith!

Merging service files

We add more transformers and a filter.

app/pom.xml (shade configuration)
<transformers>
    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
        <mainClass>com.example.app.Main</mainClass>
    </transformer>
    <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
    <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer"/>
</transformers>
<filters>
    <filter>
        <artifact>*:*</artifact>
        <excludes>
            <exclude>META-INF/MANIFEST.MF</exclude>
            <exclude>META-INF/versions/*/module-info.class</exclude>
            <exclude>module-info.class</exclude>
        </excludes>
    </filter>
</filters>

The ServicesResourceTransformer concatenates the files in META-INF/services instead of keeping the first one. The ApacheNoticeResourceTransformer merges the NOTICE files of Apache projects into one.

<filter>
    <artifact>*:*</artifact>
    <excludes>
        <exclude>META-INF/MANIFEST.MF</exclude>
        <exclude>META-INF/versions/*/module-info.class</exclude>
        <exclude>module-info.class</exclude>
    </excludes>
</filter>

Filters remove files from the merged JARs. The *:* pattern matches all artifacts. We drop the manifests of the dependencies, and the module descriptors, which describe single modules and are not valid for the merged JAR.

Both greeters are found now, and the service file lists both implementations. The manifest is the one of the application with the main class added. The remaining warning concerns the license files of Commons Lang and Commons Text; both contain the text of the Apache License 2.0, so it does not matter which one is kept.

$ mvn clean package
commons-lang3-3.20.0.jar, commons-text-1.15.0.jar define 1 overlapping resource: 
  - META-INF/LICENSE.txt
maven-shade-plugin has detected that some files are
present in two or more JARs. When this happens, only one
single version of the file is copied to the uber jar.
Usually this is not harmful and you can skip these warnings,
otherwise try to manually exclude artifacts based on
mvn dependency:tree -Ddetail=true and the above output.
See https://maven.apache.org/plugins/maven-shade-plugin/
BUILD SUCCESS
$ java -jar app/target/app-1.0.0.jar "lucy smith"
English  Hello, Lucy Smith!
Slovak   Ahoj, Lucy Smith!
$ unzip -p app/target/app-1.0.0.jar META-INF/services/com.example.api.Greeter
com.example.english.EnglishGreeter
com.example.slovak.SlovakGreeter
$ unzip -p app/target/app-1.0.0.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.5.1
Java-Version: 25
Build-Jdk-Spec: 25
Main-Class: com.example.app.Main

Relocating packages

An uber JAR that is used as a library, for example a plugin loaded by another application, can clash with the classes of the host. If the host uses a different version of Commons Text, two versions of the same classes end up on one classpath. Relocation solves this by moving the classes of a dependency into a private package.

app/pom.xml (relocations)
<relocations>
    <relocation>
        <pattern>org.apache.commons</pattern>
        <shadedPattern>com.example.shaded.commons</shadedPattern>
    </relocation>
</relocations>

All classes in the org.apache.commons package and its subpackages are moved to com.example.shaded.commons.

The JAR contains no classes in org/apache. Shade rewrote not only the relocated classes, but also all references to them in the bytecode of the application, as javap shows. The source code still imports org.apache.commons.text.WordUtils. Relocation does not change strings, so code that loads classes by name through reflection may break.

$ java -jar app/target/app-1.0.0.jar "lucy smith"
English  Hello, Lucy Smith!
Slovak   Ahoj, Lucy Smith!
$ unzip -l app/target/app-1.0.0.jar | grep -c org/apache
0
$ unzip -l app/target/app-1.0.0.jar | grep text/WordUtils
     9209  2025-12-04 15:53   com/example/shaded/commons/text/WordUtils.class
     5677  2025-11-12 18:39   com/example/shaded/commons/lang3/text/WordUtils.class
$ javap -c -cp app/target/app-1.0.0.jar com.example.app.Main | grep WordUtils
        13: invokestatic  #23                 // Method com/example/shaded/commons/text/WordUtils.capitalizeFully:(Ljava/lang/String;)Ljava/lang/String;

Minimizing the JAR

The application uses one method of Commons Text, but the uber JAR contains both libraries in full. The minimizeJar option removes the classes that the application does not use.

app/pom.xml (shade configuration)
<minimizeJar>true</minimizeJar>

Shade analyzes the bytecode, starting from the classes of the project, and keeps only the classes of the dependencies that are reachable from them.

The JAR shrinks from 654 to 224 entries. The greeters survive, because the plugin keeps the classes listed in service files. Classes loaded only through reflection are not found by the analysis; they can be kept with an includes element in a filter.

$ unzip -l app/target/app-1.0.0.jar | tail -1
  2149980                     654 files
$ mvn clean package
Minimizing jar com.example:app:jar:1.0.0
BUILD SUCCESS
$ unzip -l app/target/app-1.0.0.jar | tail -1
   812474                     224 files
$ java -jar app/target/app-1.0.0.jar "lucy smith"
English  Hello, Lucy Smith!
Slovak   Ahoj, Lucy Smith!

The dependency-reduced POM

The uber JAR contains its dependencies, so the dependencies should not be declared again for projects that use it. Shade therefore writes a dependency-reduced-pom.xml file, which lacks the shaded dependencies, and installs and deploys it instead of the original POM. The file is written into the project directory, not into target; it is usually added to .gitignore, or the plugin is configured with createDependencyReducedPom set to false for applications that are never used as dependencies.

Shade also has a trap. When we run mvn package a second time without clean, the JAR plugin finds the JAR up to date and does not rebuild it, so Shade takes the uber JAR from the previous build as the project artifact and warns about hundreds of duplicate classes. The result works, but the build should always be run with mvn clean package.

$ grep -c '<dependency>' app/pom.xml app/dependency-reduced-pom.xml
app/pom.xml:3
app/dependency-reduced-pom.xml:0
$ mvn package
--- jar:3.5.1:jar (default-jar) @ api ---
--- jar:3.5.1:jar (default-jar) @ english ---
--- jar:3.5.1:jar (default-jar) @ slovak ---
--- jar:3.5.1:jar (default-jar) @ app ---
--- shade:3.6.2:shade (default) @ app ---
app-1.0.0.jar, commons-lang3-3.20.0.jar define 423 overlapping classes and resources: 
app-1.0.0.jar, commons-lang3-3.20.0.jar, commons-text-1.15.0.jar define 1 overlapping resource: 
app-1.0.0.jar, english-1.0.0.jar define 3 overlapping classes and resources: 
app-1.0.0.jar, slovak-1.0.0.jar define 3 overlapping classes and resources: 
api-1.0.0.jar, app-1.0.0.jar define 3 overlapping classes and resources: 
app-1.0.0.jar, commons-text-1.15.0.jar define 170 overlapping classes and resources: 
BUILD SUCCESS

Useful options

ParameterDescription
transformersprocess resources while merging
filtersinclude or exclude files of artifacts
artifactSetinclude or exclude whole artifacts
relocationsmove packages
minimizeJarremove unused classes
shadedArtifactAttachedkeep the original JAR as the main artifact
shadedClassifierNameclassifier of the attached uber JAR
createDependencyReducedPomwrite the reduced POM

With shadedArtifactAttached set to true, the uber JAR gets a classifier, shaded by default, and the normal JAR stays the main artifact. This is useful for libraries that publish both variants.

Source

Apache Maven Shade Plugin

In this article we have built uber JARs with the Maven Shade Plugin, merged service files, relocated packages, and minimized the JAR.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.