Optimizing Spring Boot Fat JARs: Splitting Dependencies and Reducing Deployment Size
This article explains how to break down large Spring Boot fat JARs into a small business JAR and a shared lib directory, covering four optimization levels for single‑module and multi‑module microservice projects, and demonstrates Maven configurations that shrink deployment packages from hundreds of megabytes to a few hundred kilobytes.
Overview
With the popularity of Spring Boot, developers enjoy the simplicity of building a single executable JAR and launching it with java -jar. However, as a monolithic application grows, the JAR can reach 200‑300 MB, and a microservice architecture with 10‑20 services can easily exceed 1‑2 GB of deployment artifacts.
When a system is online, any new feature or bug‑fix requires a redeployment. For many delivery‑type projects, the first deployment or remote update often involves transferring hundreds of megabytes or even gigabytes, which is a painful bottleneck.
Imagine a critical bug is reported, developers fix the code, rebuild the package, and hand it to operations. The manager asks, "Is the update ready?" Operations can only reply, "Not yet, the deployment package is too large and still uploading...".
Why must we upload hundreds of megabytes for a tiny code change? Is there a way to optimise?
If you face this situation, keep reading – the solution may be here.
Article includes:
How to split a 200‑300 MB Spring Boot JAR into a lib directory of dependencies and a business JAR of only a few hundred KB.
How to merge the overlapping dependencies of 10‑20 microservices into a single lib directory and reduce the total deployment size from a few gigabytes to a few hundred megabytes.
Article does NOT include:
Spring Boot configuration file separation (e.g., external YAML via active profiles or Nacos).
Maven best‑practice usage – the sample projects place some configuration in the parent POM for demonstration only.
Executable JAR runtime mode support – the implementation focuses on the java -jar mode.
Optimization Levels
Level 0: Conventional Fat JAR Build
Project directory: package-optimize-level0 Main configuration:
<build>
<finalName>${project.artifactId}</finalName>
<!-- The demo puts plugin configuration in the parent POM for convenience. In a real project, move these definitions to the Spring Boot module only. -->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>Build output:
cd package-optimize-level0
mvn clean install
ls -lh package-optimize-app1/target/package-optimize-app1.jar
-rw-r--r-- 1 user wheel 16M Feb 24 21:06 package-optimize-app1.jar
java -jar package-optimize-app1.jarKey notes:
The demo only depends on spring-boot-starter-web, so the built JAR is about ten megabytes; real projects can easily reach dozens or hundreds of megabytes.
If ten microservices need deployment, the total transfer size becomes 1‑2 GB; even a single‑service update still requires transferring hundreds of megabytes.
Level 1: Common Dependency‑JAR Separation
Project directory: package-optimize-level1 Problem solved: Reduce the size of each microservice JAR so that deployment can be performed in seconds.
Main configuration:
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- Copy all dependency JARs to a lib directory -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals><goal>copy-dependencies</goal></goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<silent>true</silent>
</configuration>
</execution>
</executions>
</plugin>
<!-- Spring Boot module JAR build with empty includes to exclude all third‑party JARs from the executable JAR -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includes>
<include>
<groupId>null</groupId>
<artifactId>null</artifactId>
</include>
</includes>
<layout>ZIP</layout>
</configuration>
</plugin>
</plugins>
</build>Build output:
cd package-optimize-level1
mvn clean install
ls -lh package-optimize-app1/target/package-optimize-app1.jar
-rw-r--r-- 1 user wheel 149K Feb 24 20:56 package-optimize-app1.jar
java -jar -Djava.ext.dirs=lib package-optimize-app1.jarEffect:
The built JAR is only a few hundred KB, enabling near‑instant deployment.
For many services, the first deployment still requires transferring a few hundred MB, but subsequent updates are tiny.
Level 2: Merge All Module Dependencies into a Single lib Directory
Project directory: package-optimize-level2 Problem solved:
Combine all module dependency JARs into one lib directory; because most modules share the same dependencies, the total size stays around 200‑300 MB.
Using -Djava.ext.dirs=lib loads every JAR into each JVM, which wastes resources and can cause version conflicts.
Main configuration:
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- Add Class‑Path entries to the MANIFEST so the JAR knows where to find dependencies -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<useUniqueVersions>false</useUniqueVersions>
</manifest>
</archive>
</configuration>
</plugin>
<!-- Copy all dependencies to the shared lib directory -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals><goal>copy-dependencies</goal></goals>
<configuration>
<outputDirectory>${boot-jar-output}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<silent>false</silent>
</configuration>
</execution>
</executions>
</plugin>
<!-- Spring Boot repackage – output JARs to the same directory as the lib folder -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includes>
<include>
<groupId>null</groupId>
<artifactId>null</artifactId>
</include>
</includes>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals><goal>repackage</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>Resulting JARs contain a MANIFEST entry similar to:
Class-Path: lib/spring-boot-starter-web-2.4.3.jar lib/spring-boot-2.4.3.jar lib/spring-boot-autoconfigure-2.4.3.jar ...Build output:
cd package-optimize-level2
mvn clean install
ls -lh devops/
# lib directory (~1 MB) and three 150 KB service JARs
java -jar devops/package-optimize-app1.jarEffect:
No need for -Djava.ext.dirs=lib at runtime.
Total deployment size stays around 200‑300 MB.
Each service JAR declares its exact dependency versions in the MANIFEST, avoiding version‑conflict issues.
Level 3: Support System‑Scope Third‑Party SDK JARs
Project directory: package-optimize-level3 Problem solved:
Some SDKs are not published to a Maven repository and are added via systemPath. The maven-jar-plugin does not automatically include these in the MANIFEST.
Without special handling, the generated JAR cannot locate the SDK at runtime.
Main configuration:
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- Add Class‑Path entries, including a custom entry for system‑scope JARs -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<useUniqueVersions>false</useUniqueVersions>
</manifest>
<manifestEntries>
<Class-Path>${jar-manifestEntries-classpath}</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!-- Copy all dependencies (including system‑scope JARs) to lib -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals><goal>copy-dependencies</goal></goals>
<configuration>
<outputDirectory>${boot-jar-output}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<silent>false</silent>
</configuration>
</execution>
</executions>
</plugin>
<!-- Spring Boot repackage – same as Level 2 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includes>
<include>
<groupId>null</groupId>
<artifactId>null</artifactId>
</include>
</includes>
<layout>ZIP</layout>
</configuration>
</plugin>
</plugins>
</build>Each sub‑module defines its own properties:
<properties>
<boot-jar-output>../devops</boot-jar-output>
<jar-manifestEntries-classpath>. lib/hik-sdk-1.0.0.jar</jar-manifestEntries-classpath>
</properties>
<dependencies>
<dependency>
<groupId>com.hik</groupId>
<artifactId>hik-sdk</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/hik-sdk-1.0.0.jar</systemPath>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>The generated MANIFEST now starts with the custom class‑path entry:
Class-Path: . lib/hik-sdk-1.0.0.jar lib/spring-boot-starter-web-2.4.3.jar lib/spring-boot-2.4.3.jar ...Build output:
cd package-optimize-level3
mvn clean install
ls -lh devops/
# three 150 KB service JARs and a shared lib directory
java -jar devops/package-optimize-app1.jarFinal Result
All services share a common dependency directory; total size is only a few hundred MB, dramatically speeding up the first deployment.
Each microservice JAR is only a few hundred KB, allowing emergency bug fixes to be deployed almost instantly.
Version‑specific Class‑Path entries prevent dependency‑version conflicts between services.
Even non‑official third‑party SDKs can be packaged and loaded correctly.
Special Tips
After separating deployment components, daily updates only need to transfer a few hundred KB of business JARs. However, if a Maven dependency version changes, remember to copy the updated JAR into the shared lib directory.
Tip for minimizing changes: store the build output directory in a Git repository. Each release commits the new lib and service JARs, and the Git diff clearly shows which files changed, allowing you to transfer only the modified artifacts.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
