What is a Spring Boot executable fat jar, and how is it structured internally?
answer
- BOOT-INF/classes = your code
- BOOT-INF/lib = deps kept whole
- Main-Class=JarLauncher, Start-Class=your app
- nested jars, not merged (unlike shade)
- java -jar app.jar
basics
~10 sA single runnable jar containing your compiled classes plus all dependency jars. You run it with java -jar app.jar. Spring puts your code in BOOT-INF/classes and dependency jars in BOOT-INF/lib.
solid answer
~40 sA Spring Boot executable ("fat" or "uber") jar bundles everything needed to run the app into one file you launch with `java -jar app.jar` — no external classpath. Internally it uses a nested layout: your compiled classes and resources go under BOOT-INF/classes, and every dependency jar sits untouched under BOOT-INF/lib. The jar's manifest sets Main-Class to a Spring launcher (JarLauncher), and Start-Class to your actual @SpringBootApplication main class. When the JVM runs the jar, JarLauncher builds a classloader that can read the nested lib jars, then invokes your Start-Class main method. This layout is produced by the spring-boot-maven-plugin or the Spring Boot Gradle plugin's bootJar task, which repackages the plain jar.
code
java · 22 lines// Your Start-Class — the manifest's Start-Class points here.
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
/*
Built with spring-boot-maven-plugin's repackage goal, the resulting jar
has MANIFEST.MF:
Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: com.example.MyApp
Run it with: java -jar target/myapp-1.0.0.jar
*/go deeper
Know it's one runnable jar with your classes in BOOT-INF/classes and deps in BOOT-INF/lib, run via java -jar.
Explain the manifest split (Main-Class=JarLauncher, Start-Class=your app) and that deps stay whole.
Contrast with shade/uber merging, explain why nesting preserves resources, know the plugin goals.
Discuss trade-offs vs exploded/layered deployments, classpath.idx, and container image implications.
## What problem it solves A normal Java application needs its class files plus every dependency jar on the classpath. Distributing that means shipping many files and a launch script. A Spring Boot **executable fat jar** (also called an "uber jar") packs everything — your code and all third-party libraries — into **one file** you run with `java -jar app.jar`. ## The nested-jar layout Spring Boot does NOT explode dependency jars into loose class files. Instead it keeps each dependency as a **whole jar nested inside the outer jar**. A repackaged jar looks like: ``` app.jar ├── META-INF/ │ └── MANIFEST.MF ├── org/springframework/boot/loader/... (the loader classes, unpacked) ├── BOOT-INF/ │ ├── classes/ (YOUR compiled classes + resources) │ │ └── com/example/MyApp.class │ ├── lib/ (dependency jars, kept whole) │ │ ├── spring-core-6.x.jar │ │ └── ... │ └── classpath.idx (ordering index) └── META-INF/MANIFEST.MF ``` - **BOOT-INF/classes** — your application's own compiled `.class` files and resources (application.yml, templates, etc.). - **BOOT-INF/lib** — every dependency, each as an intact `.jar`. - The **loader classes** (`org.springframework.boot.loader.*`) live at the root, unpacked, because the JVM must be able to load them with the standard classloader before any nesting magic exists. ## Why not just merge all classes together (shade)? Tools like the Maven Shade plugin build an uber jar by **unzipping every dependency and merging classes** at the root. Spring Boot deliberately avoids that: merging can clobber files with the same path (e.g., two libraries each shipping `META-INF/spring.factories` or service-loader files under `META-INF/services`), and it destroys the identity of each dependency. Keeping jars **nested and whole** preserves those resources and each library's structure. ## The manifest The outer jar's `META-INF/MANIFEST.MF` contains: ``` Main-Class: org.springframework.boot.loader.launch.JarLauncher Start-Class: com.example.MyApp ``` - **Main-Class** is what the JVM invokes for `java -jar`. It points at Spring's launcher, NOT your app. - **Start-Class** is your real `@SpringBootApplication` class with the `main` method. (In Spring Boot 3.2+ the loader package is `org.springframework.boot.loader.launch.JarLauncher`; earlier versions used `org.springframework.boot.loader.JarLauncher`.) ## How it runs 1. `java -jar app.jar` → JVM reads Main-Class → runs `JarLauncher.main`. 2. JarLauncher creates a classloader that can read classes both from `BOOT-INF/classes` and from the **nested jars** in `BOOT-INF/lib`. 3. It reads Start-Class from the manifest and reflectively calls your app's `main`, now on a classpath where all dependencies resolve. ## Who builds it - **Maven:** `spring-boot-maven-plugin`'s `repackage` goal (bound to the `package` phase) takes the plain jar and rewrites it into this layout. - **Gradle:** the Spring Boot plugin's `bootJar` task. ## When to use Fat jars are the default for microservices and containerized apps — one artifact, one command. For deploying into a traditional servlet container you'd instead build a `war` (the plugin supports that too).
- Why does Spring Boot keep dependency jars whole under BOOT-INF/lib instead of merging their classes?Merging (shading) can overwrite same-path resources like META-INF/spring.factories or META-INF/services entries when two libraries collide. Keeping each jar nested and intact preserves those resources and each library's identity.
- What command produces the fat jar in Maven vs Gradle?Maven: the spring-boot-maven-plugin repackage goal (runs during `package`). Gradle: the Spring Boot plugin's `bootJar` task.
saying these in an interview costs you the question
- Saying all dependency classes are merged/exploded into one flat classpath (that's shade, not Spring Boot).
- Claiming Main-Class points at your @SpringBootApplication class (it points at JarLauncher).
- Thinking a fat jar must be unzipped before running — you run it directly with java -jar.