skip to content

Executable fat jar layout

A Boot jar keeps dependencies as nested jars under BOOT-INF and boots through JarLauncher, with your class named as Start-Class in the manifest. Explaining why it is not a shaded uber-jar is the point of the question.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What is a Spring Boot executable fat jar, and how is it structured internally?

level: juniorimportance: must knowfreq 70%

answer

  1. BOOT-INF/classes = your code
  2. BOOT-INF/lib = deps kept whole
  3. Main-Class=JarLauncher, Start-Class=your app
  4. nested jars, not merged (unlike shade)
  5. java -jar app.jar

basics

~10 s

A 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 s

A 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
java
// 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

for a junior

Know it's one runnable jar with your classes in BOOT-INF/classes and deps in BOOT-INF/lib, run via java -jar.

for a middle

Explain the manifest split (Main-Class=JarLauncher, Start-Class=your app) and that deps stay whole.

for a senior

Contrast with shade/uber merging, explain why nesting preserves resources, know the plugin goals.

for a principal

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.

context

open as a page

What is JarLauncher and what exactly does it do at startup?

level: middleimportance: must knowfreq 55%

basics

~20 s

JarLauncher is Spring Boot's bootstrap class named as Main-Class in the manifest. The JVM runs it first; it builds a classloader that can read the nested jars in BOOT-INF/lib, then calls your app's Start-Class main method.

open as a page

Explain the manifest's Main-Class vs Start-Class in a Spring Boot jar. Why the two-level indirection?

level: middleimportance: should knowfreq 45%

basics

~10 s

Main-Class is what the JVM runs for java -jar — Spring sets it to JarLauncher. Start-Class is your real @SpringBootApplication main. JarLauncher first fixes the classpath for nested jars, then calls Start-Class.

open as a page

Contrast Spring Boot's nested-jar fat jar with a shaded/uber jar. What problems does nesting avoid?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A shaded jar unzips every dependency and merges all classes into one flat jar, which can overwrite duplicate resources. Spring Boot keeps each dependency as a whole nested jar under BOOT-INF/lib, preserving resources and library identity.

open as a page

For containerized deployment, how would you optimize a Spring Boot fat jar's startup and image caching? Discuss exploded and layered jars.

level: principalimportance: nice to knowfreq 25%

basics

~10 s

Split the fat jar into Docker layers (dependencies vs app code) so rarely-changing layers stay cached, and optionally run the exploded jar (extracted BOOT-INF) with JarLauncher to reduce nested-jar classloading overhead and speed startup.

open as a page