Search code examples
javamavenmaven-shade-plugin

How do I create a Fat Jar with Maven Shade?


I am trying to create a fat jar using Maven. Everyone says the Shade plugin is the simplest way to do it, so that is what I am trying. I've tried a good dozen example projects. All of them are slightly different in what XML tags are used and how the XML is nested, which is confusing and frustrating, but also none of them worked for me, and they are all so obscure I cannot tell WHY they don't work.

I have tried to boil the problem down to a minimum viable Hello World project.

Here is the project structure:

  ├── pom.xml
  └── src
      └── main
          └── java
              └── org
                  └── example
                      └── Hello.java

My Hello.java looks like this:

package org.example;

import gnu.getopt.Getopt;

public class Hello {
    public static void main(String[] args) {
        Getopt g = new Getopt("hello", args, "x");

        int c;
        while ((c = g.getopt()) != -1) {
            switch (c) {
                case 'x' -> System.out.println("Hello X!");
                default -> System.out.println("Error " + c);
            }
        }
        System.out.println("Goodbye");
    }
};

And my pom.xml looks like this:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>hello-fat-jar</artifactId>
  <version>0.1-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target> 
    <maven.compiler.release>17</maven.compiler.release> 
  </properties>

  <dependencies>
    <!-- https://mvnrepository.com/artifact/gnu.getopt/java-getopt -->
    <dependency>
      <groupId>gnu.getopt</groupId>
      <artifactId>java-getopt</artifactId>
      <version>1.0.13</version>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>

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

So far so good. I do a mvn clean package:

$ mvn clean package
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------------< org.example:hello-fat-jar >----------------------
[INFO] Building hello-fat-jar 0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from central: https://repo.maven.apache.org/maven2/gnu/getopt/java-getopt/1.0.13/java-getopt-1.0.13.pom
Downloaded from central: https://repo.maven.apache.org/maven2/gnu/getopt/java-getopt/1.0.13/java-getopt-1.0.13.pom (2.0 kB at 9.5 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/gnu/getopt/java-getopt/1.0.13/java-getopt-1.0.13.jar
Downloaded from central: https://repo.maven.apache.org/maven2/gnu/getopt/java-getopt/1.0.13/java-getopt-1.0.13.jar (60 kB at 1.8 MB/s)
[INFO] 
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ hello-fat-jar ---
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello-fat-jar ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory HOME/src/java/hello-fat-jar/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello-fat-jar ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to HOME/src/java/hello-fat-jar/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello-fat-jar ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory HOME/src/java/hello-fat-jar/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello-fat-jar ---
[INFO] No sources to compile
[INFO] 
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello-fat-jar ---
[INFO] No tests to run.
[INFO] 
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello-fat-jar ---
[INFO] Building jar: HOME/src/java/hello-fat-jar/target/hello-fat-jar-0.1-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.595 s
[INFO] Finished at: 2023-03-24T16:30:35-07:00
[INFO] ------------------------------------------------------------------------

I can see the dependency has been downloaded:

$ ls ~/.m2/repository/gnu/getopt/java-getopt/1.0.13
java-getopt-1.0.13.jar  java-getopt-1.0.13.jar.sha1  java-getopt-1.0.13.pom  java-getopt-1.0.13.pom.sha1  _remote.repositories

But it did not create a fat jar:

$ jar tf target/hello-fat-jar-0.1-SNAPSHOT.jar
META-INF/
META-INF/MANIFEST.MF
org/
org/example/
org/example/Hello.class
META-INF/maven/
META-INF/maven/org.example/
META-INF/maven/org.example/hello-fat-jar/
META-INF/maven/org.example/hello-fat-jar/pom.xml
META-INF/maven/org.example/hello-fat-jar/pom.properties

When I try to run it complains about no main manifest attribute, which supposedly the shade plugin was supposed to set:

$ java -jar target/hello-fat-jar-0.1-SNAPSHOT.jar
no main manifest attribute, in target/hello-fat-jar-0.1-SNAPSHOT.jar

I can get around the main manifest by adding a maven-jar-plugin with a <mainClass>, but it still isn't a fat jar and can't find the Getopt dependency. Also the shade plugin is supposed to set the main class and it is not doing so. I would like to understand why it is not working, and fix it, rather than rely on a hacky band-aid.

But the code and the jar is good! I can run it with an explicit classpath:

$ java -cp target/hello-fat-jar-0.1-SNAPSHOT.jar:$HOME/.m2/repository/gnu/getopt/java-getopt/1.0.13/java-getopt-1.0.13.jar org.example.Hello -x
Hello X!
Goodbye

(For whatever it is worth, this is the cleanest tutorial I could find: https://javahelps.com/how-to-create-a-fat-jar-using-maven)

What am I doing wrong? How can I create a fat jar using the shade plugin?

UPDATE: following tgdavies' comment, I removed the <pluginManagement> tags and everything works as expected. (pluginManagement came from the maven archetype:generate goal.)


Solution

  • The answer is from the comment by tgdavies. The <pluginManagement> tag interferes with the Shade plugin. For this example, it was causing two bugs:

    • The main manifest attribute was not getting set, preventing the jar from being executable.
    • The dependencies were not getting incorporated into the jar (i.e. a fat jar was not being constructed).

    Removing <pluginManagement> resolved both of these issues. More information can be found at pluginManagement interferes with shade plugin.