Packaging runnable applications

Maven · Advanced · 12 min

Shipping a service means producing something that runs with java -jar; each approach has trade-offs.

A plain mvn package jar contains only your classes; dependencies are not included and the manifest has no Main-Class. Options for runnable artifacts:

  • Jar plugin + classpath: set <manifest><mainClass> and <addClasspath>true</addClasspath>, and copy dependencies next to it with dependency:copy-dependencies. Clean and fast, but you ship a folder.
  • maven-shade-plugin: merges all dependency classes into one uber jar (java -jar app.jar). Use the ManifestResourceTransformer for Main-Class and ServicesResourceTransformer to merge META-INF/services files, or service loading (JDBC drivers, Jackson modules) silently breaks. Shade can also relocate packages to avoid conflicts, which is how libraries bundle dependencies safely. Signed jars must have their signatures excluded.
  • maven-assembly-plugin with the jar-with-dependencies descriptor: simpler but merges files naively.
  • spring-boot-maven-plugin repackage: nests dependency jars inside a launcher jar, avoiding merging problems; also builds OCI images with build-image.
  • jlink/jpackage plugins produce custom runtimes and native installers.

Resources from src/main/resources are copied into the jar; enable <filtering> to inject ${project.version} into files like application.properties, but keep binaries out of filtered directories.

Set project.build.outputTimestamp for reproducible builds so the same source yields a byte-identical jar, which the Maven Release and verification tooling increasingly expect.

Example

<build>
  <plugins>
    <plugin>
      <artifactId>maven-shade-plugin</artifactId>
      <version>3.6.0</version>
      <executions>
        <execution>
          <phase>package</phase>
          <goals><goal>shade</goal></goals>
          <configuration>
            <createDependencyReducedPom>false</createDependencyReducedPom>
            <transformers>
              <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                <mainClass>com.acme.App</mainClass>
              </transformer>
              <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
            </transformers>
            <filters>
              <filter>                     <!-- drop signatures of signed dependencies -->
                <artifact>*:*</artifact>
                <excludes><exclude>META-INF/*.SF</exclude><exclude>META-INF/*.DSA</exclude><exclude>META-INF/*.RSA</exclude></excludes>
              </filter>
            </filters>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
<!-- mvn package && java -jar target/shop-api-1.0.0-SNAPSHOT.jar -->

Remember

  • A default jar has no dependencies and no Main-Class
  • Shade merges classes; use transformers for manifests and META-INF/services
  • Spring Boot's repackage nests jars instead of merging
  • Set project.build.outputTimestamp for reproducible jars

Watch out for

  • Shading without ServicesResourceTransformer, so JDBC drivers or Jackson modules are not found at run time.

Check yourself

  1. Why can a shaded jar fail to find a JDBC driver that works in the IDE?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: maven.apache.org/plugins/maven-shade-plugin/