How Spring Boot Packs and Launches Your Application: Inside the Fat Jar
This article explains how Spring Boot uses the Maven plugin to create an executable fat JAR, details the internal archive layout, the repackage process, the custom class loader and URL handling, and compares JarLauncher with WarLauncher for both JAR and WAR deployments.
Packaging Plugin and Core Methods
Spring Boot projects declare spring-boot-maven-plugin in pom.xml. Executing mvn clean package generates two files: spring-learn-0.0.1-SNAPSHOT.jar and spring-learn-0.0.1-SNAPSHOT.jar.original. The plugin defines five goals (repackage, run, start, stop, build‑info); the default goal is repackage, which repackages the built JAR into an executable fat JAR and renames the original.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>Repackage Implementation
The repackage goal invokes RepackageMojo.execute(), which in turn calls the private repackage() method. The method creates a Repackager, filters runtime dependencies, builds a Libraries object, obtains a LaunchScript, and finally calls repackager.repackage(target, libraries, launchScript) to produce the fat JAR. After repackaging, the original JAR is renamed with the .original suffix.
private void repackage() throws MojoExecutionException {
// maven generated jar will be renamed with .original suffix
Artifact source = getSourceArtifact();
File target = getTargetFile();
Repackager repackager = getRepackager(source.getFile());
Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(),
getFilters(getAdditionalFilters()));
Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack, getLog());
try {
LaunchScript launchScript = getLaunchScript();
repackager.repackage(target, libraries, launchScript);
} catch (IOException ex) {
throw new MojoExecutionException(ex.getMessage(), ex);
}
updateArtifact(source, target, repackager.getBackupFile());
}Fat JAR Directory Structure
After repackaging, the executable JAR contains a well‑defined layout:
spring-boot-learn-0.0.1-SNAPSHOT.jar
├── META-INF
│ └── MANIFEST.MF
├── BOOT-INF
│ ├── classes // compiled application classes
│ └── lib // third‑party dependency JARs
└── org
└── springframework
└── boot
└── loader
└── springboot launcher classesMANIFEST.MF Content
The manifest records the entry points:
Manifest-Version: 1.0
Implementation-Title: spring-learn
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.secbro2.learn.SpringLearnApplication
Main-Class: org.springframework.boot.loader.JarLauncher
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.1.5.RELEASE
Created-By: Maven Archiver 3.4.0Archive Abstraction
Spring Boot abstracts a resource container via the org.springframework.boot.loader.archive.Archive interface. Two concrete implementations exist: JarFileArchive – used when the application runs from a JAR. ExplodedArchive – used when the application runs from an expanded directory.
public interface Archive extends Iterable<Archive.Entry> {
URL getUrl() throws MalformedURLException;
Manifest getManifest() throws IOException;
List<Archive> getNestedArchives(EntryFilter filter) throws IOException;
}JarLauncher
The manifest’s Main-Class points to org.springframework.boot.loader.JarLauncher. JarLauncher extends ExecutableArchiveLauncher, which in turn extends Launcher. Its main simply creates a JarLauncher instance and calls launch(args).
public class JarLauncher extends ExecutableArchiveLauncher {
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
} ExecutableArchiveLauncherbuilds an Archive for the current JAR, creates a LaunchedURLClassLoader that loads all JARs under BOOT-INF/lib, and finally invokes the application’s Start-Class main method.
WarLauncher
When the packaging type is war, the build modifies the Maven configuration to exclude the embedded Tomcat and adds a SpringBootServletInitializer subclass. The resulting JAR’s manifest sets Main-Class: org.springframework.boot.loader.WarLauncher. WarLauncher differs only in the directories it scans ( WEB-INF/classes, WEB-INF/lib, and WEB-INF/lib-provided).
public class WarLauncher extends ExecutableArchiveLauncher {
private static final String WEB_INF = "WEB-INF/";
private static final String WEB_INF_CLASSES = WEB_INF + "classes/";
private static final String WEB_INF_LIB = WEB_INF + "lib/";
private static final String WEB_INF_LIB_PROVIDED = WEB_INF + "lib-provided/";
@Override
public boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(WEB_INF_CLASSES);
} else {
return entry.getName().startsWith(WEB_INF_LIB) ||
entry.getName().startsWith(WEB_INF_LIB_PROVIDED);
}
}
public static void main(String[] args) throws Exception {
new WarLauncher().launch(args);
}
}URLStreamHandler and JarURLConnection
Spring Boot registers a custom org.springframework.boot.loader.jar.Handler for the jar: protocol. This handler can parse URLs containing multiple !/ separators (the “jar‑in‑jar” case) and returns a custom JarURLConnection implementation that reads the nested entry’s data.
// Simplified view of the custom JarURLConnection
public InputStream getInputStream() throws IOException {
connect();
if (this.jarEntryName.isEmpty()) {
throw new IOException("no entry name specified");
}
return this.jarEntryData.getInputStream();
}ClassLoader Resource Loading Flow
When a class or resource is requested, the following chain is executed:
LaunchedURLClassLoader.loadClass URL.getContent() URL.openConnection() Handler.openConnection(URL)(the Spring Boot custom handler) JarURLConnection.getInputStream() returns the underlying JarEntryData The Launcher builds a URLClassPath from the array of URLs pointing to every JAR under BOOT-INF/lib (or the equivalent WAR directories). Each URL is wrapped in a Loader that knows how to fetch resources via the custom handler.
Running from IDE or Exploded Directory
In an IDE, the application’s own main method is executed directly, and the classpath already contains all dependency JARs, so Spring Boot’s custom launcher is bypassed. When the fat JAR is exploded into a directory and launched, Spring Boot detects the exploded layout, creates an ExplodedArchive, and follows the same launch steps as for a packaged JAR.
Summary of the Launch Process
Spring Boot builds a fat JAR that bundles BOOT-INF/classes and BOOT-INF/lib. JarLauncher (or WarLauncher) creates a LaunchedURLClassLoader that loads all nested JARs.
The custom URLStreamHandler and JarURLConnection enable “jar‑in‑jar” resource access.
The launcher finally invokes the application’s Start-Class main method.
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.
Senior Brother's Insights
A public account focused on workplace, career growth, team management, and self-improvement. The author is the writer of books including 'SpringBoot Technology Insider' and 'Drools 8 Rule Engine: Core Technology and Practice'.
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.
