Search code examples
javamavenmaven-shade-pluginexec-maven-plugin

Maven shade executable uber jar NoClassDefFoundError


I am creating a executable uber jar with the maven shade plugin, it has a dependency on some classes in a local system jar (mobile-dock-support.jar) that I want it to include during the package phase, so I have the shade plugin configured like so

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.4.2</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer
                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>uk.xxx.mobiledock.PCSyncApplication</mainClass>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>uk.xxx</groupId>
            <artifactId>mobile-dock-support</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <scope>system</scope>
            <systemPath>${project.build.directory}/../src/main/libs/mobile-dock-support.jar</systemPath>
        </dependency>
    </dependencies>
</plugin>

It seems to package the jar fine but when I run the jar using java -jar xxx.jar it gives me the following

java.lang.NoClassDefFoundError: uk/xxx/mobiledock/MobileDockException
        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
        at java.lang.Class.privateGetMethodRecursive(Unknown Source)
        at java.lang.Class.getMethod0(Unknown Source)
        at java.lang.Class.getMethod(Unknown Source)
        at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
        at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)
Caused by: java.lang.ClassNotFoundException: uk.xxx.mobiledock.MobileDockException
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 7 more
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main"

I've also setup the exec maven plugin, and running mvn exec:java -Dexec.mainClass=uk.xxx.mobiledock.PCSyncApplication runs the program as expected.

Here's how exec maven plugin is setup

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>1.4.0</version>
    <executions>
        <execution>
            <goals>
                <goal>java</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <includePluginDependencies>true</includePluginDependencies>
        <mainClass>uk.xxx.mobiledock.PCSyncApplication</mainClass>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>uk.xxx</groupId>
            <artifactId>mobile-dock-support</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <scope>system</scope>
            <systemPath>${project.build.directory}/../src/main/libs/mobile-dock-support.jar</systemPath>
        </dependency>
    </dependencies>
</plugin>

Solution

  • OK then it appears the issue is the shade plugin doesn't copy system dependencies to the uber jar. There are a couple of 'correct' ways of fixing this

    1. Install the dependency to your local maven repository using mvn install

    2. Use a repository manager such as Nexus

    If however you're like me and didn't have the option of the above two as it's a collaborative project and there was no option for something like Nexus, then here is what I did to get it working

    Use the maven dependency plugin to copy any system dependencies to somewhere relative to the packaged jar, eg.

    <plugin>
        <artifactId>maven-dependency-plugin</artifactId>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>copy-dependencies</goal>
                </goals>
                <configuration>
                    <outputDirectory>${project.build.directory}/lib</outputDirectory>
                    <includeArtifactIds>mobile-dock-support</includeArtifactIds>
                </configuration>
            </execution>
        </executions>
    </plugin>
    

    Add the copied jars as part of the class path in the uber jar's manifest file eg.

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.4.2</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>shade</goal>
                </goals>
                <configuration>
                    <transformers>
                        <transformer
                            implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                            <manifestEntries>
                                <Main-Class>uk.xxx.mobiledock.PCSyncApplication</Main-Class>
                                <Class-Path>lib/mobile-dock-support-${mobile-dock-support.version}.jar</Class-Path>
                            </manifestEntries>
                        </transformer>
                    </transformers>
                </configuration>
            </execution>
        </executions>
    </plugin>