skip to content

Packaging, Build Plugins & DevTools

Turning the application into something deployable: the executable fat jar, layered jars, the build plugins, buildpack images, DevTools and CDS-based startup. Interviewers ask here when the role touches Docker and CI.

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

explore

questions

30

How do you build a container image from a Spring Boot application without writing a Dockerfile, and what mechanism makes this possible?

level: juniorimportance: must knowfreq 70%

answer

  1. bootBuildImage / spring-boot:build-image
  2. Cloud Native Buildpacks, no Dockerfile
  3. Paketo builder default
  4. needs Docker daemon
  5. layered jar = good caching

basics

~10 s

Run the built-in goal: ./gradlew bootBuildImage (Gradle) or ./mvnw spring-boot:build-image (Maven). Spring Boot uses Cloud Native Buildpacks to turn your app into an OCI (Docker-compatible) container image, no Dockerfile needed.

solid answer

~40 s

Spring Boot ships a build-image goal in both plugins: `bootBuildImage` for Gradle and `spring-boot:build-image` for Maven. It uses Cloud Native Buildpacks (by default the Paketo builder image) to produce an OCI-compliant image. The buildpacks inspect your compiled app, detect it's a JVM/Spring Boot app, install a suitable JRE, and layer the exploded jar for good caching — all without you authoring or maintaining a Dockerfile. You need a running Docker (or other) daemon reachable for the build. The result is a normal image you can `docker run`. Compared to a hand-written Dockerfile, you get sensible, community-maintained defaults (memory calculation, security-minded base image, layering) for free, at the cost of some direct control over the image internals.

code

kotlin · 16 lines
kotlin
// build.gradle.kts — the Spring Boot Gradle plugin registers bootBuildImage.
// Run it with:  ./gradlew bootBuildImage

plugins {
    java
    id("org.springframework.boot") version "3.5.0"
    id("io.spring.dependency-management") version "1.1.6"
}

// No extra config needed for a basic build:
// the task depends on bootJar and uses the default Paketo builder.

tasks.named<org.springframework.boot.gradle.tasks.bundling.BootBuildImage>("bootBuildImage") {
    // Optional: give the image a friendly name instead of the project default.
    imageName.set("example.com/team/${project.name}:${project.version}")
}

go deeper

for a junior

Know the two goal names and that buildpacks replace the Dockerfile.

for a middle

Explain the Paketo builder default and that a Docker daemon is required.

for a senior

Discuss layered-jar caching and the detect/build lifecycle.

for a principal

Weigh buildpacks vs hand-written Dockerfiles vs Jib for a platform strategy.

**OCI image** = the Open Container Initiative standard image format — what Docker, Podman, Kubernetes all run. "Build an OCI image" just means "produce a container image." **The problem it solves:** Traditionally you package a Spring Boot app as an executable jar, then hand-write a `Dockerfile` (`FROM eclipse-temurin`, `COPY app.jar`, `ENTRYPOINT java -jar ...`). That Dockerfile is boilerplate you must maintain, keep secure, and optimize for layer caching yourself. **Cloud Native Buildpacks (CNB):** an open specification (originally from Heroku/Pivotal, now a CNCF project) for turning source or compiled artifacts into runnable container images *without a Dockerfile*. A **builder** is an image that bundles a set of **buildpacks** plus lifecycle logic. Each buildpack has two phases: **detect** ("does this app apply to me?") and **build** ("contribute a layer"). For a Spring Boot jar the relevant buildpacks detect a JVM app, install a JRE, and configure the entry point. **Spring Boot's integration:** Both official plugins expose a goal that invokes the CNB lifecycle against a **builder image**: - Gradle: `./gradlew bootBuildImage` (task type `BootBuildImage`). - Maven: `./mvnw spring-boot:build-image` (bound to the `package` phase if you configure it, or run directly). By default it uses a **Paketo Buildpacks** builder (e.g. `paketobuildpacks/builder-jammy-java-tiny` in recent Boot versions; the exact default tracks the Boot version). Paketo is the CNB implementation Spring recommends. **What happens under the hood:** 1. The plugin first builds your application jar (it depends on the `bootJar`/`repackage` output). 2. It pulls the **builder** image and a **run** image (the base the final app runs on). 3. It runs the CNB **lifecycle** which detects the app, downloads a JRE, and **explodes the jar into layers** matching Spring Boot's layered-jar model (dependencies, spring-boot-loader, snapshot dependencies, application classes) so that changing your code only rebuilds the top layer. 4. It writes the finished image to the local Docker daemon (default) under a name derived from your project. **Prerequisites / gotchas:** - A container runtime the plugin can talk to (Docker daemon by default; the connection is configurable via `docker` settings / `DOCKER_HOST`). The build is *not* pure-JVM — it needs that daemon. - The first run is slow (pulls builder + run images); later runs reuse cache volumes. - The default image name is `docker.io/library/<artifactId>:<version>` (Maven) / `docker.io/library/<project.name>:<version>` (Gradle) — often you'll override it. - It produces an image; it does **not** push by default (see the publish option). **When to use:** you want maintainable, secure, well-layered images without owning a Dockerfile — ideal for standard Spring Boot services. **When not to:** you need very specific OS packages, custom base images, or air-gapped builds where pulling Paketo builders is awkward (though these are configurable).

  • Does bootBuildImage require a Dockerfile in your project?
    No. That is the whole point — Cloud Native Buildpacks generate the image layers themselves. You never write or maintain a Dockerfile.
  • Does the build need Docker installed?
    It needs a reachable container daemon (Docker by default). The plugin talks to it via the Docker API; you can point it elsewhere with the `docker` connection settings or DOCKER_HOST. The build is not pure-JVM.

saying these in an interview costs you the question

  • Claiming you still need a Dockerfile for bootBuildImage to work
  • Saying it uses Jib or Dockerfile-based builds (it uses Cloud Native Buildpacks / Paketo)
  • Thinking it runs with no container runtime at all

context

open as a page

How do you run a Spring Boot application locally with the Maven or Gradle plugin, and what do those plugins add over a plain build?

level: juniorimportance: must knowfreq 75%

basics

~20 s

With Maven run mvn spring-boot:run; with Gradle run ./gradlew bootRun. Both compile and start your @SpringBootApplication main class in dev. The plugins also build an executable jar containing all dependencies so java -jar just works.

open as a page

What is Spring Boot DevTools and what does it give you during development?

level: juniorimportance: must knowfreq 55%

basics

~20 s

spring-boot-devtools is an optional dev-only dependency. It automatically restarts your app when class files change, refreshes the browser via LiveReload, applies developer-friendly property defaults (like disabling template caching), and is left out of production jars.

open as a page

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

level: juniorimportance: must knowfreq 70%

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.

open as a page

How do you control the resulting image's name and push it directly to a registry from bootBuildImage / spring-boot:build-image?

level: middleimportance: must knowfreq 65%

basics

~10 s

Set the image name (imageName in Gradle, <image><name> in Maven, or --imageName= / -Dspring-boot.build-image.imageName=). To push, enable publish (publish = true / <publish>true</publish>) and provide registry credentials via docker.publishRegistry.

open as a page

What exactly does the spring-boot-maven-plugin `repackage` goal do, and how does it relate to Gradle's `bootJar` task?

level: middleimportance: must knowfreq 68%

basics

~20 s

repackage takes the plain jar Maven already built and rewrites it into an executable 'fat' jar: dependencies nested under BOOT-INF/lib, your classes under BOOT-INF/classes, plus a launcher in the manifest. Gradle's bootJar task builds the same executable jar directly.

open as a page

Walk through how you enable Project CDS in a Spring Boot 3.3+ app end to end.

level: middleimportance: must knowfreq 30%

basics

~10 s

Three steps: (1) extract the fat jar to a folder, (2) do a training run with -XX:ArchiveClassesAtExit=app.jsa and -Dspring.context.exit=onRefresh to produce the archive, (3) run normally with -XX:SharedArchiveFile=app.jsa.

open as a page

How does Spring Boot ensure DevTools never runs in production, and how do you declare the dependency correctly?

level: middleimportance: must knowfreq 45%

basics

~20 s

You declare DevTools as developmentOnly (Gradle) or optional (Maven) so it isn't packaged into the production jar. As a safety net, DevTools also disables itself automatically when it detects the app was started from a fully packaged jar rather than exploded classes.

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

What are the four default layers in a Spring Boot layered jar, and why are they ordered the way they are?

level: middleimportance: must knowfreq 55%

basics

~10 s

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

open as a page

How does `java -Djarmode=layertools extract` work, and how do you wire it into a multi-stage Dockerfile?

level: seniorimportance: must knowfreq 50%

basics

~20 s

java -Djarmode=layertools -jar app.jar extract reads layers.idx and writes one folder per layer. In a builder Docker stage you run it, then COPY each folder into the final image separately (stable first) and start with JarLauncher.

open as a page

What is Class Data Sharing (CDS) and how does it speed up Spring Boot startup?

level: juniorimportance: should knowfreq 35%

basics

~20 s

CDS saves the classes the JVM loads into a shared archive file. On the next startup the JVM memory-maps that archive instead of reading and parsing each class from the jar, so class loading is faster and startup time drops.

open as a page

What is a Spring Boot layered jar and why does it help with Docker image builds?

level: juniorimportance: should knowfreq 45%

basics

~20 s

A layered jar splits a Spring Boot fat jar into groups (layers) that change at different rates. In a Dockerfile you copy each layer separately so slow-changing dependency layers stay cached and only your fast-changing code is rebuilt.

open as a page

Explain the roles of -XX:ArchiveClassesAtExit, -XX:SharedArchiveFile, and the .jsa file in a Spring Boot CDS setup.

level: middleimportance: should knowfreq 20%

basics

~20 s

The .jsa is the shared archive file holding pre-parsed class metadata. -XX:ArchiveClassesAtExit writes that archive when the training JVM exits. -XX:SharedArchiveFile points a later JVM at the archive to memory-map it at startup. One writes, one reads.

open as a page

What are DevTools' LiveReload and development-time property defaults, and why do they exist?

level: middleimportance: should knowfreq 35%

basics

~20 s

LiveReload is an embedded server (port 35729) that tells a browser extension to refresh the page when a resource changes. Property defaults are dev-friendly overrides DevTools applies automatically — mainly disabling template/resource caching — so your edits show up immediately.

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

How do you enable/verify layered-jar packaging in the Spring Boot build plugin, and how can you inspect a jar's layers?

level: middleimportance: should knowfreq 35%

basics

~20 s

Layering is on by default since Spring Boot 2.4 via the Gradle/Maven Boot plugin. You can toggle it in the plugin's layered config. To inspect, run java -Djarmode=layertools -jar app.jar list, or unzip and open BOOT-INF/layers.idx.

open as a page

How do you customize which Paketo builder is used and control things like the JVM version or JVM buildpack settings during a buildpacks build?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Override the builder (e.g. builder = "paketobuildpacks/builder-jammy-base") and/or runImage. Pass build-time settings to the buildpacks via environment map entries, e.g. BP_JVM_VERSION=21 to pick the JRE version.

open as a page

Why do buildpacks-built Spring Boot images cache well, and how does the layered-jar format relate to that? How does a GraalVM native image build differ?

level: seniorimportance: should knowfreq 35%

basics

~20 s

The buildpack explodes the executable jar into Spring Boot's layered structure (dependencies, loader, snapshot deps, application) so changing only your code rebuilds just the top layer. A native build (BP_NATIVE_IMAGE=true) instead AOT-compiles to a single native executable — tiny, fast-starting, no JVM.

open as a page

What is the `classifier` configuration in the Spring Boot build plugins, and when would you set one?

level: seniorimportance: should knowfreq 45%

basics

~20 s

By default the executable jar replaces the plain jar under the same coordinates. Setting a classifier (e.g. exec) gives the executable jar a suffixed name so both the plain library jar and the executable jar are kept and can be published side by side.

open as a page

How do you exclude specific dependencies from a Spring Boot executable jar, and why would you?

level: seniorimportance: should knowfreq 38%

basics

~10 s

In the spring-boot-maven-plugin configuration use <excludes> (by groupId/artifactId), <excludeGroupIds>, or <excludeArtifactIds> so those jars aren't nested into BOOT-INF/lib. You do this when a dependency is provided by the runtime environment or shouldn't be shipped.

open as a page

What happens to a CDS archive when the classpath or JDK changes, and how do you verify CDS is actually active at runtime?

level: seniorimportance: should knowfreq 22%

basics

~20 s

The archive is tied to the exact classpath and JDK it was built with. If either changes, the JVM detects a mismatch, ignores the archive, and falls back to normal class loading (no crash). Verify with -Xshare:on (fails if unusable) or -Xlog:cds logging.

open as a page

How does DevTools' automatic restart work internally, and why is it faster than restarting the JVM?

level: seniorimportance: should knowfreq 40%

basics

~20 s

DevTools uses two classloaders: a base classloader for unchanging library jars and a restart classloader for your own code. On a file change it discards and rebuilds only the restart classloader and the application context, so libraries stay loaded and startup is much faster than a cold JVM start.

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

When would you reach for CDS to improve Spring Boot startup, and how does it fit alongside heavier options like AOT/native?

level: principalimportance: should knowfreq 18%

basics

~20 s

Use CDS when you want cheaper, faster startup while keeping an ordinary JVM (full reflection, easy debugging, no special build). It gives a moderate startup cut with almost no risk and stacks with AOT; native images give bigger gains but cost far more build/complexity.

open as a page

As a platform owner, how do you decide between buildpacks (bootBuildImage) and a hand-written Dockerfile, and what do you do to make buildpacks builds reproducible and secure in CI?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Buildpacks give maintenance-free, secure, well-layered images and automatic base-image patching, at the cost of control. Dockerfiles give full control but you own security/layering. For reproducibility, pin the builder/run images by digest, control the JVM version, manage registry creds as secrets, and scan the output.

open as a page

In Gradle, how do `bootJar` and the standard `jar` task relate, and how do layered jars change container packaging?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

bootJar builds the executable fat jar; the standard jar task builds a plain library jar (given a -plain classifier by default since Boot 2.5). Layered jars split the fat jar into ordered layers (dependencies, app code) so Docker can cache stable layers and rebuild only what changed.

open as a page

As a principal engineer, what risks and edge cases would you flag about DevTools, especially remote DevTools and restart-classloader pitfalls?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Never enable remote DevTools against production — it can execute code on the server. Also watch for cross-restart ClassCastExceptions from the two-classloader model, lost in-memory state on each restart, and exploded-run deployments where DevTools may not self-disable. Keep it strictly developmentOnly.

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

When would you choose the layertools Dockerfile approach over buildpacks (bootBuildImage), and how does layering relate to newer optimizations like CDS/AOT?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Use the layertools Dockerfile when you need full control over the base image, JVM flags, or non-standard layout. Use buildpacks (bootBuildImage) for zero-Dockerfile, best-practice images. Layering speeds up builds/pushes; CDS/AOT speed up app startup — they are complementary, not alternatives.

open as a page