What are the four default layers in a Spring Boot layered jar, and why are they ordered the way they are?
answer
- dep -> loader -> snapshot -> application
- stable at bottom, volatile at top
- invalidation cascades upward in Docker
- layerOrder mirrors change frequency
- loader is separate because it's at jar root
basics
~10 sIn order: dependencies, spring-boot-loader, snapshot-dependencies, application. They go from least-likely-to-change to most-likely-to-change so Docker keeps the stable ones cached and only rebuilds the top application layer.
solid answer
~40 sThe defaults, ordered stable-to-volatile, are: (1) `dependencies` — released third-party jars; (2) `spring-boot-loader` — the `org/springframework/boot/loader` launcher classes, which change only on a Boot upgrade; (3) `snapshot-dependencies` — SNAPSHOT libraries, which change more often than releases; (4) `application` — your own classes and resources, which change on every build. The ordering is deliberate: Docker stacks image layers and invalidates every layer above a changed one, so the things that rarely change are placed first (bottom) and stay cached, while your volatile application code sits last (top). Only that small top layer is re-stored and re-pushed on a routine code change. The mapping of files to layers lives in `BOOT-INF/layers.idx`, and you can customize both the layer set and the order in the Gradle/Maven plugin config if your change-frequency profile differs.
code
kotlin · 24 lines// build.gradle.kts — customizing layers to add a 'company-dependencies' layer
// for your own fast-changing internal libs, between third-party deps and app code.
tasks.named<org.springframework.boot.gradle.tasks.bundling.BootJar>("bootJar") {
layered {
dependencies {
intoLayer("snapshot-dependencies") { include("*:*:*SNAPSHOT") }
intoLayer("company-dependencies") { include("com.mycompany:*") }
intoLayer("dependencies")
}
application {
intoLayer("spring-boot-loader") { include("org/springframework/boot/loader/**") }
intoLayer("application")
}
layerOrder.set(
listOf(
"dependencies",
"spring-boot-loader",
"snapshot-dependencies",
"company-dependencies",
"application",
),
)
}
}go deeper
Just remember the four names and that stable comes first, code last.
Explain the change-frequency rationale and Docker's upward cascade invalidation.
Discuss layers.idx as an ordered index and customizing layers via the plugin for internal libs.
Design a layering scheme matched to a monorepo's actual change cadence and CI cache topology; weigh layer count vs. cache granularity.
### The four default layers (order is the answer) Spring Boot's build plugins define, by default, four layers listed from **least** to **most** frequently changing: | Order | Layer | Contents | Change frequency | |-------|-------|----------|------------------| | 1 | **`dependencies`** | Released (non-SNAPSHOT) third-party jars under `BOOT-INF/lib/` | Very low — only on a dependency bump | | 2 | **`spring-boot-loader`** | The `org/springframework/boot/loader/**` classes at the jar root (the launcher that makes `java -jar` work) | Very low — only on a Spring Boot version change | | 3 | **`snapshot-dependencies`** | SNAPSHOT dependencies (e.g. `1.0.0-SNAPSHOT`) | Medium — SNAPSHOTs are volatile by nature | | 4 | **`application`** | Your code and resources: `BOOT-INF/classes/**`, `META-INF/**` | High — every build | ### Why this exact order A Docker image is a stack of layers; when a layer's content changes, that layer **and every layer created after it** are invalidated and must be rebuilt/re-pushed. Therefore you want the immutable stuff at the bottom and the churny stuff at the top. Dependencies (huge, stable) go first so they remain a cache hit across nearly all builds; your application code (tiny, changes constantly) goes last so its invalidation costs almost nothing. Placing `snapshot-dependencies` *above* released `dependencies` reflects that SNAPSHOTs change more often than releases but still less often than your own code. ### `layers.idx` The file `BOOT-INF/layers.idx` inside the jar is an **ordered** index mapping each layer name to the paths it owns. `layertools` reads it during extraction; the order in the file *is* the intended Docker copy order. ### Customizing layers Both plugins let you redefine layers and their content. Gradle example: ```groovy bootJar { layered { application { intoLayer("spring-boot-loader") { include("org/springframework/boot/loader/**") } intoLayer("application") } dependencies { intoLayer("snapshot-dependencies") { include("*:*:*SNAPSHOT") } intoLayer("company-dependencies") { include("com.mycompany:*") } intoLayer("dependencies") } layerOrder = ["dependencies", "spring-boot-loader", "snapshot-dependencies", "company-dependencies", "application"] } } ``` A common real-world refinement is splitting your own frequently-changing internal libraries into their own layer between third-party deps and application code. ### Gotchas - **Order in `layerOrder` must be consistent with real change frequency** — misordering (e.g. putting `application` early) defeats the caching benefit. - The `spring-boot-loader` layer exists because those launcher classes live at the jar *root*, not under `BOOT-INF/lib/`, so they need their own bucket. - Every path in the jar must map to exactly one layer; unmatched files cause a build error, which is why the default config ends with catch-all `intoLayer("application")` / `intoLayer("dependencies")` rules. - Customization only affects packaging/caching — never runtime behavior or classpath semantics.
- Why is spring-boot-loader its own layer instead of part of dependencies?Those launcher classes live at the jar root (outside BOOT-INF/lib), and they change only on a Spring Boot version bump — a different, very-low change cadence — so they get their own bucket for clean caching.
- How would you carve out your own frequently-changing internal libraries?Define a custom layer (e.g. company-dependencies) via intoLayer with a group filter, and place it in layerOrder above application but below third-party dependencies, so a released third-party bump still doesn't invalidate app code.
saying these in an interview costs you the question
- Listing the layers in the wrong order (e.g. application first)
- Claiming layering compresses or shrinks the jar
- Saying you cannot customize the layers