How to Shrink Spring Boot Fat JARs from Hundreds MB to a Few KB
This article explains how to break down large Spring Boot fat JARs by extracting dependencies into a shared lib directory and packaging only the business code, reducing single‑service JARs to a few hundred kilobytes and cutting total deployment size from gigabytes to a few hundred megabytes.
Overview
With Spring Boot, a single java -jar command can launch an application, but as projects grow the generated fat JAR often reaches 200‑300 MB, and a micro‑service architecture can easily exceed 1‑2 GB for the whole system. Deploying such large files slows down updates, especially for urgent bug fixes.
Level 0 – Regular Fat JAR Build
Project directory:
package-optimize-level0 <build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>Build and run:
cd package-optimize-level0
mvn clean install
ls -lh package-optimize-app1/target/package-optimize-app1.jar
java -jar package-optimize-app1/target/package-optimize-app1.jarThe resulting JAR is about 16 MB for a minimal example, but real projects often produce tens or hundreds of megabytes.
Level 1 – Separate Dependencies into lib Directory
Project directory:
package-optimize-level1 <build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- copy all dependencies 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>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<silent>true</silent>
</configuration>
</execution>
</executions>
</plugin>
<!-- Spring Boot repackage without any dependencies inside the JAR -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includes>
<include>null:null</include>
</includes>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals><goal>repackage</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>Build and run:
cd package-optimize-level1
mvn clean install
ls -lh package-optimize-app1/target/package-optimize-app1.jar # 149K
java -jar -Djava.ext.dirs=lib package-optimize-app1/target/package-optimize-app1.jarThe business JAR is now only a few hundred kilobytes, while all dependencies reside in lib/.
Level 2 – Merge All Services' Dependencies into a Single lib Directory
Project directory:
package-optimize-level2 <build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- add Class‑Path entry to MANIFEST -->
<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 a shared 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 without embedded dependencies -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includes><include>null:null</include></includes>
<layout>ZIP</layout>
</configuration>
<executions>
<execution><goals><goal>repackage</goal></goals></execution>
</executions>
</plugin>
</plugins>
</build>Result:
No need for -Djava.ext.dirs=lib at runtime.
All services share a common lib/ directory; total deployment size is a few hundred megabytes instead of gigabytes.
Each service’s MANIFEST contains a precise Class-Path to avoid version conflicts.
Level 3 – Handle Non‑Official Third‑Party JARs (system scope)
Project directory:
package-optimize-level3 <build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- add custom Class‑Path entries 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 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 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includes><include>null:null</include></includes>
<layout>ZIP</layout>
</configuration>
<executions>
<execution><goals><goal>repackage</goal></goals></execution>
</executions>
</plugin>
</plugins>
</build>
<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>Resulting MANIFEST Class-Path includes the custom SDK JAR followed by all Spring Boot dependencies, ensuring the system‑scope JAR is available at runtime.
Final Outcome
All services share a common lib/ directory; total size is only a few hundred megabytes, dramatically speeding up first‑time deployment.
Each micro‑service JAR is reduced to a few hundred kilobytes, allowing near‑instant updates for urgent bug fixes.
Explicit Class-Path entries prevent version‑conflict issues between services.
Non‑official third‑party SDKs can be packaged and loaded without Maven repository support.
Special Tip
If a Maven dependency version changes, remember to copy the updated JAR into the shared lib/ directory. Storing the deployment resources in a Git repository makes it easy to track which files have changed between releases, minimizing the amount of data that needs to be transferred.
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.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
