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.
package com.example.api;
public interface Greeter {
String language();
String greet(String name);
}
The interface is a service that the other modules provide.
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.
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.
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.
<?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.
<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.
<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.
<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
| Parameter | Description |
|---|---|
transformers | process resources while merging |
filters | include or exclude files of artifacts |
artifactSet | include or exclude whole artifacts |
relocations | move packages |
minimizeJar | remove unused classes |
shadedArtifactAttached | keep the original JAR as the main artifact |
shadedClassifierName | classifier of the attached uber JAR |
createDependencyReducedPom | write 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
In this article we have built uber JARs with the Maven Shade Plugin, merged service files, relocated packages, and minimized the JAR.
Author
List all Java tutorials.