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.

Senior Brother's Insights
Senior Brother's Insights
Senior Brother's Insights
How Spring Boot Packs and Launches Your Application: Inside the Fat Jar

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 classes

MANIFEST.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.0

Archive 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);
    }
}
ExecutableArchiveLauncher

builds 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Spring BootJarLauncherMaven PluginFat JarURLStreamHandlerWarLauncher
Senior Brother's Insights
Written by

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'.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.