The Maven Shade Plugin packages our classes and the classes of all our dependencies into one uber JAR (also called a fat JAR), which we can start with java -jar. It can also rename the packages of a dependency inside that JAR, which is called shading, and that gives the plugin its name.
We use the Maven Shade Plugin when we ship a command-line tool, a batch job or a small server as a single file, so the target machine needs only a JVM and no lib folder or long classpath.
The following example adds the plugin to the pom.xml of a small app and sets the main class in the JAR manifest.
<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.howtodoinjava.shade.BookApp</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
After mvn clean package, the file target/book-cli-1.0.0.jar contains our code plus every dependency, and java -jar target/book-cli-1.0.0.jar starts it with no extra classpath. Notice that we do not change the jar packaging or call the plugin by hand. The shade goal runs in the package phase, right after the normal JAR is built, and replaces that JAR with the uber JAR.
Next, we look at what the plugin puts into the JAR. After that, we fix the problems that show up once a project has several dependencies, such as service files that overwrite each other, signed JARs, version conflicts and JAR size.
1. Thin JAR vs Uber JAR
A normal Maven build creates a thin JAR, which holds only the classes from src/main/java and the files from src/main/resources. The dependencies stay in the local Maven repository, so the JVM finds them only when we list every dependency JAR on the classpath.
When we start a thin JAR without its dependencies, the JVM fails at the first line that uses a dependency class. In our demo app, that line creates a Gson object.
$ java -cp target/original-book-cli-1.0.0.jar com.howtodoinjava.shade.BookApp
Exception in thread "main" java.lang.NoClassDefFoundError: com/google/gson/Gson
at com.howtodoinjava.shade.BookApp.main(BookApp.java:15)
Caused by: java.lang.ClassNotFoundException: com.google.gson.Gson
An uber JAR unpacks the content of every runtime dependency JAR and writes it into one new JAR next to our own classes. The JVM finds all classes in that one file, so the classpath is the JAR itself. Dependencies with the provided or test scope are not part of the runtime classpath, so the plugin leaves them out.
Say a team writes a nightly export tool that runs on an operations server. With a thin JAR, the operations team copies 40 JARs and keeps a start script with the classpath in sync. With an uber JAR, they copy one file and run java -jar export-tool.jar.

Maven has three common ways to build a runnable JAR, and each one fits a different kind of project.
| Maven Shade Plugin | Maven Assembly Plugin | Spring Boot Maven Plugin | |
|---|---|---|---|
| JAR layout | all classes unpacked into one JAR | all classes unpacked into one JAR | dependency JARs kept as nested JARs in BOOT-INF/lib |
| Merges files with the same name | yes, with transformers | no, one file wins | not needed, JARs stay separate |
| Renames dependency packages | yes, with relocations | no | no |
| Typical use | CLI tools, libraries, plain Java apps | simple apps, ZIP or TAR distributions | Spring Boot applications |
We pick the Maven Shade Plugin for plain Java apps that have more than a couple of dependencies, because the transformers handle the duplicate files that the Assembly Plugin overwrites. For a Spring Boot application, the Spring Boot Maven Plugin is the better choice, because it keeps the dependency JARs intact.
2. Creating an Executable Uber JAR
The following example is a small command-line app called book-cli. It turns a Book record into JSON with Gson and prints the JDBC drivers it finds on the classpath. The project depends on Gson, the H2 database and HSQLDB, and we build it with Maven 3.9.16, JDK 25 and maven-shade-plugin 3.6.2.
2.1. Project Setup
The main class uses one class from each kind of dependency. The DriverManager.drivers() call matters later, in section 3, because JDBC drivers register themselves through service files.
public class BookApp {
record Book(String title, String author, int year) {}
public static void main(String[] args) {
Book book = new Book("Clean Code", "Robert C. Martin", 2008);
String json = new Gson().toJson(book);
System.out.println(json);
List<String> drivers = DriverManager.drivers()
.map(d -> d.getClass().getName())
.sorted()
.toList();
System.out.println("JDBC drivers: " + drivers);
}
}
The pom.xml lists the three dependencies and the plugin from the intro. We set the Java release with the maven.compiler.release property, as described in setting the Java version in Maven.
<groupId>com.howtodoinjava</groupId>
<artifactId>book-cli</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.14.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.5.252</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.7.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.16.0</version>
</plugin>
<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.howtodoinjava.shade.BookApp</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
The ManifestResourceTransformer writes the Main-Class entry into META-INF/MANIFEST.MF. Without it, java -jar stops with “no main manifest attribute”. We can add more manifest lines with a manifestEntries element, as we do in section 7.
2.2. Building and Running the JAR
We run the build from the project folder. The log shows the jar goal first, and the shade goal right after it.
$ mvn clean package
...
[INFO] --- jar:3.5.0:jar (default-jar) @ book-cli ---
[INFO] Building jar: /tmp/book-cli/target/book-cli-1.0.0.jar
[INFO]
[INFO] --- shade:3.6.2:shade (default) @ book-cli ---
[INFO] Dependency-reduced POM written at: /tmp/book-cli/dependency-reduced-pom.xml
[WARNING] Discovered module-info.class. Shading will break its strong encapsulation.
...
[INFO] Replacing original artifact with shaded artifact.
[INFO] BUILD SUCCESS
The build succeeds, but it prints several warnings, and we cover them in section 4. Let us start the JAR.
$ java -jar target/book-cli-1.0.0.jar
{"title":"Clean Code","author":"Robert C. Martin","year":2008}
JDBC drivers: [org.h2.Driver]
The JSON line proves that Gson is inside the JAR. The driver list is wrong, though, because the project depends on two JDBC drivers and the app finds only H2.
2.3. Files Created by the Build
The shade goal leaves three files behind, and each one has a different purpose.
- The file target/book-cli-1.0.0.jar is the uber JAR (about 4.5 MB in our build). The plugin replaces the normal JAR with it, so mvn install and mvn deploy publish the uber JAR.
- The file target/original-book-cli-1.0.0.jar is the thin JAR from the jar goal (about 4 KB), kept with the original- prefix.
- The file dependency-reduced-pom.xml in the project folder is a copy of our POM without the dependencies that the uber JAR contains. Maven publishes it together with the uber JAR, so a project that depends on our JAR does not download Gson, H2 and HSQLDB a second time.
We can check the content of the uber JAR with the jar tool. The output is long, so we filter it for a few entries.
$ jar tf target/book-cli-1.0.0.jar | grep -E "Gson.class|Driver.class|java.sql.Driver"
com/google/gson/Gson.class
META-INF/services/java.sql.Driver
org/h2/Driver.class
org/hsqldb/jdbc/JDBCDriver.class
The HSQLDB driver class is in the JAR, so the missing driver in the output is not a missing class. The problem is the single META-INF/services/java.sql.Driver file.
3. Merging META-INF/services Files
Many libraries register their implementations through the Java ServiceLoader mechanism. A library ships a text file named after an interface in META-INF/services, and the file lists the implementation classes. For example, DriverManager reads every META-INF/services/java.sql.Driver file on the classpath to find the JDBC drivers.
In a thin-JAR setup, each driver JAR has its own copy of the file, so the JVM sees two files. In an uber JAR, both files have the same path, and without a transformer the plugin copies only the first one. The build log says so in a warning.
[WARNING] h2-2.5.252.jar, hsqldb-2.7.4.jar define 1 overlapping resource:
[WARNING] - META-INF/services/java.sql.Driver
The same bug shows up with any library that uses service files, for example JDBC drivers, Jackson modules, logging backends and Jakarta EE providers. Say a reporting app supports both PostgreSQL and MySQL. It works in the IDE, but in production the uber JAR throws an SQLException with the message “No suitable driver found” for one of the databases.
The ServicesResourceTransformer merges all service files with the same name into one file, so the uber JAR keeps every implementation. We add it next to the manifest transformer.
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.howtodoinjava.shade.BookApp</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
After a new build, the merged file lists both drivers, and the app finds both.
$ unzip -p target/book-cli-1.0.0.jar META-INF/services/java.sql.Driver
org.h2.Driver
org.hsqldb.jdbc.JDBCDriver
$ java -jar target/book-cli-1.0.0.jar
{"title":"Clean Code","author":"Robert C. Martin","year":2008}
JDBC drivers: [org.h2.Driver, org.hsqldb.jdbc.JDBCDriver]
The plugin has more transformers for other files that libraries share. We need them only when the build log reports an overlap for such a file.
| Transformer | Use it for |
|---|---|
| ManifestResourceTransformer | Main-Class and other manifest entries |
| ServicesResourceTransformer | merging META-INF/services files |
| AppendingTransformer | appending text files with the same name, e.g. META-INF/spring.handlers |
| XmlAppendingTransformer | appending XML files with the same name |
| PropertiesTransformer | merging .properties files, with an ordinal that decides which value wins |
| ApacheLicenseResourceTransformer | dropping duplicate LICENSE files |
| ApacheNoticeResourceTransformer | merging NOTICE files into one |
4. Fixing Overlapping Resource Warnings
After the services fix, the build still prints warnings about other files that several JARs contain. Most of them are harmless, but two kinds of files need a filter, namely module-info.class files and signature files of signed JARs.
[WARNING] Discovered module-info.class. Shading will break its strong encapsulation.
[WARNING] error_prone_annotations-2.48.0.jar, gson-2.14.0.jar define 1 overlapping classes:
[WARNING] - META-INF.versions.9.module-info
[WARNING] book-cli-1.0.0.jar, error_prone_annotations-2.48.0.jar, gson-2.14.0.jar, h2-2.5.252.jar, hsqldb-2.7.4.jar define 1 overlapping resource:
[WARNING] - META-INF/MANIFEST.MF
A module-info.class file describes one Java module. An uber JAR mixes many libraries, so the module descriptor of one library is wrong for the whole JAR. We run the uber JAR on the classpath anyway, so we drop these files.
The MANIFEST.MF warning is harmless. Every JAR has a manifest, and the ManifestResourceTransformer builds the manifest of the uber JAR from our project’s manifest, so we can ignore that one warning.
4.1. Signed JARs and SecurityException
Some libraries, for example Bouncy Castle, ship as signed JARs. A signed JAR contains .SF, .RSA or .DSA files in META-INF, and these files hold digests of the original JAR content. When the plugin copies them into the uber JAR, the digests no longer match, and the JVM refuses to start the app.
$ java -jar target/book-cli-1.0.0.jar
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.SecurityException: Invalid signature file digest for Manifest main attributes
The fix for both problems is a filters element that excludes these files from every dependency. The wildcard in the artifact element stands for any group ID and any artifact ID, so the filter applies to all JARs.
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>module-info.class</exclude>
<exclude>META-INF/versions/*/module-info.class</exclude>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
Removing the signature files means the uber JAR is no longer signed. Some libraries need their signature at runtime. For example, the Oracle JDK loads a JCE security provider such as Bouncy Castle only from a signed JAR, so we keep such a library outside the uber JAR and add it to the classpath instead.
5. Relocating Packages to Avoid Version Conflicts
Shading in the strict sense means renaming the packages of a dependency inside the uber JAR. The plugin moves the classes to the new package and rewrites every reference to them in our bytecode and in the other dependency classes.
We need relocation when two versions of the same library would end up on one classpath. For example, we build a reporting library that uses Gson 2.14, and a team adds it to an app that already uses an old Gson version. Only one com.google.gson.Gson class can load, so either our library or the app calls a method that does not exist in the loaded version and fails with NoSuchMethodError.
The following relocations element moves Gson into a package that belongs to us. The class relocation page lists more options, such as excludes inside a relocated package.
<relocations>
<relocation>
<pattern>com.google.gson</pattern>
<shadedPattern>com.howtodoinjava.shaded.gson</shadedPattern>
</relocation>
</relocations>
Our source code still imports com.google.gson.Gson. The plugin rewrites the bytecode during the build, so the uber JAR contains only the relocated class, and the app prints the same JSON.
$ jar tf target/book-cli-1.0.0.jar | grep "gson/Gson.class"
com/howtodoinjava/shaded/gson/Gson.class
We relocate a dependency only when a version conflict is real, because relocated classes are harder to debug. Stack traces show the new package names, and code that loads classes by a name in a string, such as Class.forName(“com.google.gson.Gson”), does not see the rename.
6. Other Useful Options
The shade goal has many parameters. The ones in the table change which files the build produces or what goes into the uber JAR.
| Option | Default | What happens when we change it |
|---|---|---|
| shadedArtifactAttached | false | With true, the normal JAR stays as book-cli-1.0.0.jar and the uber JAR is added as book-cli-1.0.0-shaded.jar |
| shadedClassifierName | shaded | Sets the suffix of the attached uber JAR |
| createDependencyReducedPom | true | With false, no dependency-reduced-pom.xml is written |
| minimizeJar | false | With true, classes that our code does not reference are removed |
| finalName | the artifact ID | Sets a different file name for the uber JAR |
We set shadedArtifactAttached to true when other projects use our JAR as a normal dependency and only the release download needs the uber JAR. We set createDependencyReducedPom to false in apps that nobody depends on, because the extra file in the project folder has no use there.
The minimizeJar option needs care. In our project, it shrank the JAR from 4.5 MB to 554 KB, but the app printed JDBC drivers: []. The plugin keeps only the classes it can reach from our code, and DriverManager loads the driver classes by the names in the service file, so the plugin removed them.
To keep such classes, we add a filter with an include pattern for each affected artifact. The plugin keeps every class that a filter includes.
<minimizeJar>true</minimizeJar>
<filters>
<filter>
<artifact>com.h2database:h2</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>org.hsqldb:hsqldb</artifact>
<includes>
<include>**</include>
</includes>
</filter>
</filters>
With these filters, the app finds both drivers again, but the JAR is back at 4.4 MB, because the two drivers are most of its size. We turn on minimizeJar only when a test runs against the uber JAR, so a removed class shows up in the build and not in production.
7. Complete Maven Shade Plugin Configuration
The final plugin configuration combines the fixes from section 3 to section 5. It sets the main class and an extra manifest entry, merges the service files, removes module descriptors and signature files, and relocates Gson.
<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>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>module-info.class</exclude>
<exclude>META-INF/versions/*/module-info.class</exclude>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>com.google.gson</pattern>
<shadedPattern>com.howtodoinjava.shaded.gson</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.howtodoinjava.shade.BookApp</mainClass>
<manifestEntries>
<Implementation-Version>1.0.0</Implementation-Version>
</manifestEntries>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
With this configuration, the build log shows only the harmless MANIFEST.MF warning, and the manifest of the uber JAR contains our entries.
$ unzip -p target/book-cli-1.0.0.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.5.0
Java-Version: 25
Build-Jdk-Spec: 25
Main-Class: com.howtodoinjava.shade.BookApp
Implementation-Version: 1.0.0
8. Maven Shade Plugin FAQs
8.1. What Is the Difference Between the Maven Shade Plugin and the Assembly Plugin?
Both plugins build a JAR that contains all dependencies, but only the Maven Shade Plugin merges files with the same name and renames packages. The jar-with-dependencies descriptor of the Assembly Plugin copies one of the duplicate files and drops the rest, so the service file problem from section 3 appears there too. The Assembly Plugin is a good choice when we build a ZIP or TAR distribution with scripts and config files next to the JARs.
8.2. What Is dependency-reduced-pom.xml and Can I Disable It?
The dependency-reduced-pom.xml file is the POM that Maven publishes with the uber JAR, and it lists only the dependencies that are not inside the uber JAR. Yes, we can disable it with createDependencyReducedPom set to false. We keep it in libraries that other projects depend on, because without it those projects download the shaded dependencies a second time.
8.3. Why Does My Uber JAR Throw NoClassDefFoundError or ClassNotFoundException?
The missing class is either in a dependency with the provided or test scope or removed by minimizeJar or a filter. We check the dependency scope in the POM, and we search the uber JAR with jar tf for the class. A similar error appears when the plugin itself misses a class during the build, which we covered in this Maven Shade Plugin error fix.
9. Conclusion
The Maven Shade Plugin turns a project and its runtime dependencies into one JAR that starts with java -jar. The minimal setup needs the shade goal and the ManifestResourceTransformer with our main class.
Most real projects need two more settings. The ServicesResourceTransformer keeps every META-INF/services entry, so JDBC drivers and other plugins keep working, and a filter removes module descriptors and the signature files that otherwise cause a SecurityException.
Relocation and minimizeJar solve real problems, version conflicts and JAR size, but both change class names or remove classes. We turn them on only with a test that runs the uber JAR.
10. References
- Apache Maven Shade Plugin
- shade goal parameters
- Resource Transformers
- Relocating Classes
- Selecting Contents for Uber JAR
- ServiceLoader Javadoc (Java 25)
- JAR File Specification (Java 25)
Happy Learning !!
I am getting an error while executing the package goal
“A required class was missing while executing org.apache.maven.plugins:maven-shade-plugin:2.4.3:shade: org/codehaus/plexus/util/xml/XmlStreamWriter”