Understanding Spring Boot Executable JAR Packaging and the Role of JarLauncher
This article explains how Spring Boot's spring-boot-maven-plugin creates an executable JAR, describes the internal JAR structure, the function of the JarLauncher class, and details the custom class loader mechanisms that enable Spring Boot applications to run directly with java -jar.
Spring Boot provides the spring-boot-maven-plugin to package an application into an executable JAR; adding this plugin to the pom.xml enables the build:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>After packaging, the generated executable-jar-1.0-SNAPSHOT.jar has the following internal structure:
├── META-INF
│ ├── MANIFEST.MF
│ └── maven
│ └── spring.study
│ └── executable-jar
│ ├── pom.properties
│ └── pom.xml
├── lib
│ ├── aopalliance-1.0.jar
│ ├── classmate-1.1.0.jar
│ ├── spring-boot-1.3.5.RELEASE.jar
│ ├── spring-boot-autoconfigure-1.3.5.RELEASE.jar
│ ├── ...
├── org
│ └── springframework
│ └── boot
│ └── loader
│ ├── ExecutableArchiveLauncher$1.class
│ ├── ...
└── spring
└── study
└── executablejar
└── ExecutableJarApplication.classThe JAR can be started directly with:
java -jar executable-jar-1.0-SNAPSHOT.jarThe fat JAR contains four types of files:
META-INF folder: program entry, with MANIFEST.MF describing the JAR.
lib directory: third‑party dependency JARs.
Spring Boot loader related code.
The module’s own compiled classes.
The MANIFEST.MF includes, among other entries, the following:
Manifest-Version: 1.0
Implementation-Title: executable-jar
Implementation-Version: 1.0-SNAPSHOT
Archiver-Version: Plexus Archiver
Built-By: Format
Start-Class: spring.study.executablejar.ExecutableJarApplication
Implementation-Vendor-Id: spring.study
Spring-Boot-Version: 1.3.5.RELEASE
Created-By: Apache Maven 3.2.3
Build-Jdk: 1.8.0_20
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncherThe Main-Class is org.springframework.boot.loader.JarLauncher , which is invoked when the JAR is run with java -jar . JarLauncher is a utility class provided by Spring Boot Loader that launches the application’s Start-Class inside the fat JAR.
Spring Boot Loader Abstract Classes
Abstract class Launcher is the base for various launchers (JarLauncher, WarLauncher, PropertiesLauncher) and works together with Archive . Archive abstracts archive files; JarFileArchive represents a JAR file, providing methods such as getUrl() and getManifest() . JarFile wraps a JAR and creates Entry objects for each file inside.
An example URL for a JarFileArchive is:
jar:file:/Users/format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/Inside the JAR, entries include:
META-INF/
META-INF/MANIFEST.MF
spring/
spring/study/
...
spring/study/executablejar/ExecutableJarApplication.class
lib/spring-boot-starter-1.3.5.RELEASE.jar
lib/spring-boot-1.3.5.RELEASE.jar
...When a JAR contains nested JARs, the path uses the !/ separator, which is handled by the custom URL protocol org.springframework.boot.loader.jar.Handler .
JarLauncher Execution Process
The main method of JarLauncher simply constructs the launcher and calls launch(args) :
public static void main(String[] args) {
// Construct JarLauncher and invoke its launch method with the console arguments
new JarLauncher().launch(args);
}During construction, JarLauncher calls the parent ExecutableArchiveLauncher which creates a JarFileArchive . The launch method performs:
protected void launch(String[] args) {
try {
// Register the custom URL protocol handler
JarFile.registerUrlProtocolHandler();
// Build a ClassLoader from all archives (project JAR + lib JARs)
ClassLoader classLoader = createClassLoader(getClassPathArchives());
// Determine the main class from the manifest (Start-Class)
launch(args, getMainClass(), classLoader);
} catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
}
}The getMainClass() method reads the Start-Class attribute from the manifest:
public String getMainClass() throws Exception {
Manifest manifest = getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}The overloaded launch creates a MainMethodRunner , starts a new thread, sets the context class loader, and runs the application's main method:
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
Thread runnerThread = new Thread(runner);
runnerThread.setContextClassLoader(classLoader);
runnerThread.setName(Thread.currentThread().getName());
runnerThread.start();
}The MainMethodRunner.run() loads the start class, finds its main method, and invokes it:
@Override
public void run() {
try {
Class
mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
if (mainMethod == null) {
throw new IllegalStateException(this.mainClassName + " does not have a main method");
}
mainMethod.invoke(null, new Object[]{this.args});
} catch (Exception ex) {
UncaughtExceptionHandler handler = Thread.currentThread().getUncaughtExceptionHandler();
if (handler != null) {
handler.uncaughtException(Thread.currentThread(), ex);
}
throw new RuntimeException(ex);
}
}After the start class’s main executes, the Spring container is created and the embedded servlet container starts.
Custom Class Loader: LaunchedURLClassLoader
LaunchedURLClassLoader overrides loadClass to implement three loading steps:
private Class
doLoadClass(String name) throws ClassNotFoundException {
// 1) Try the root class loader (e.g., ExtClassLoader)
try {
if (this.rootClassLoader != null) {
return this.rootClassLoader.loadClass(name);
}
} catch (Exception ex) { /* ignore */ }
// 2) Try to find the class locally in the JAR archives
try {
findPackage(name);
Class
cls = findClass(name);
return cls;
} catch (Exception ex) { /* ignore */ }
// 3) Fallback to standard loading hierarchy
return super.loadClass(name, false);
}The local findClass method searches the URLs of the nested JARs using the custom org.springframework.boot.loader.jar.Handler protocol:
protected Class
findClass(final String name) throws ClassNotFoundException {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction
>() {
public Class
run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
throw new ClassNotFoundException(name);
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
}A test demonstrates registering the URL handler and loading classes from the nested JARs:
// Register the custom URL protocol handler
JarFile.registerUrlProtocolHandler();
// Construct LaunchedURLClassLoader with two URLs pointing to nested JARs
LaunchedURLClassLoader classLoader = new LaunchedURLClassLoader(
new URL[]{
new URL("jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-loader-1.3.5.RELEASE.jar!/"),
new URL("jar:file:/Users/Format/Develop/gitrepository/springboot-analysis/springboot-executable-jar/target/executable-jar-1.0-SNAPSHOT.jar!/lib/spring-boot-1.3.5.RELEASE.jar!/")
},
LaunchedURLClassLoaderTest.class.getClassLoader()
);
// Load classes from the nested JARs
classLoader.loadClass("org.springframework.boot.loader.JarLauncher");
classLoader.loadClass("org.springframework.boot.SpringApplication");
// Load a class that will be found by the parent loader
classLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");Role of Spring Boot Loader
Spring Boot defines its own conventions for executable JARs: third‑party dependencies are placed under /lib , URLs use the custom jar:file:...!/ syntax handled by org.springframework.boot.loader.jar.Handler , and the JAR’s Main-Class is JarLauncher (or WarLauncher for WAR files). These launchers start a new thread that runs the user’s SpringApplication . All of these features are assembled by the spring-boot-maven-plugin .
PS: If you find this sharing useful, feel free to like and watch.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
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.