Running & Packaging
A Spring Boot application is built to be self-contained: one artifact that includes your code, your dependencies, and an embedded server. This page covers the day-to-day ways to run the app during development and how to package it into an executable JAR (or WAR) for deployment, including the layered layout that makes Docker images efficient.
Running during development
While iterating, run the app straight from the build tool. The Boot plugin compiles your code and launches the embedded server in one step.
# Maven
./mvnw spring-boot:run
# Gradle
./gradlew bootRun
You can pass program arguments and Spring properties on the command line:
./mvnw spring-boot:run -Dspring-boot.run.arguments=--server.port=9090
Tip: Add Spring Boot DevTools for automatic restarts on code changes, it makes
spring-boot:runfeel near-instant during development.
Building an executable JAR
For deployment you build a standalone, executable JAR, often called a fat JAR or uber JAR because it contains all dependencies. The spring-boot-maven-plugin (or Boot Gradle plugin) repackages the ordinary build output into this runnable form.
# Maven -> target/demo-0.0.1-SNAPSHOT.jar
./mvnw clean package
# Gradle -> build/libs/demo-0.0.1-SNAPSHOT.jar
./gradlew bootJar
Run it with the standard java -jar command, no application server needed:
java -jar target/demo-0.0.1-SNAPSHOT.jar
Output:
:: Spring Boot :: (v3.5.0)
INFO Starting DemoApplication using Java 21
INFO Tomcat started on port 8080 (http) with context path '/'
INFO Started DemoApplication in 1.31 seconds (process running for 1.6)
Override configuration at launch time with arguments, which take precedence over packaged properties:
java -jar target/demo-0.0.1-SNAPSHOT.jar --server.port=80 --spring.profiles.active=prod
Inside the fat-jar layout
A Boot executable JAR is not a plain JAR. It uses a custom layout and launcher so your classes and nested dependency JARs load correctly.
demo-0.0.1-SNAPSHOT.jar
├── META-INF/
│ └── MANIFEST.MF # Main-Class: org.springframework.boot.loader.launch.JarLauncher
│ # Start-Class: com.devcraftly.demo.DemoApplication
├── org/springframework/boot/loader/... # the Boot launcher classes
└── BOOT-INF/
├── classes/ # your compiled code + resources
├── lib/ # dependency JARs (nested, not unpacked)
└── classpath.idx # ordering index
When you run java -jar, the manifest’s Main-Class points at Spring Boot’s JarLauncher, which sets up a class loader able to read the nested JARs in BOOT-INF/lib, then hands off to your Start-Class.
Note: Because dependencies stay as nested JARs, the build is fast and reproducible. You do not (and should not) extract them into a single flat set of classes the way the old “shade” approach did.
Building a WAR
If you must deploy to an external servlet container (Tomcat, WildFly), package a WAR instead. Set war packaging and make your main class extend SpringBootServletInitializer.
<packaging>war</packaging>
@SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder app) {
return app.sources(DemoApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
You also mark the embedded server starter as provided so the external container’s servlet runtime is used. The resulting WAR still runs standalone with java -jar, but can also be dropped into a server’s webapps.
Warning: Prefer JAR packaging for new applications. WAR + external server adds operational complexity and is usually only justified by an existing deployment platform you cannot change. See Spring vs Spring Boot.
Layered JARs for Docker
For container images, an ordinary fat JAR copied in one Docker layer means any code change re-ships all dependencies. Spring Boot solves this with layered jars, which split the contents into layers ordered from least- to most-frequently changing:
| Layer | Contents | Changes |
|---|---|---|
dependencies | Release dependencies | Rarely |
spring-boot-loader | Boot loader classes | Rarely |
snapshot-dependencies | SNAPSHOT dependencies | Sometimes |
application | Your classes and resources | Every build |
Inspect and extract layers with the launcher tools:
java -Djarmode=tools -jar target/demo-0.0.1-SNAPSHOT.jar extract --layers --destination extracted
A layer-aware Dockerfile then copies each layer separately, so rebuilds reuse cached dependency layers:
FROM eclipse-temurin:21-jre AS builder
WORKDIR /app
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=builder /app/extracted/dependencies/ ./
COPY --from=builder /app/extracted/spring-boot-loader/ ./
COPY --from=builder /app/extracted/snapshot-dependencies/ ./
COPY --from=builder /app/extracted/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
Tip: You can skip writing a
Dockerfileentirely with./mvnw spring-boot:build-image, which uses Cloud Native Buildpacks to produce an optimized, layered OCI image. The full container workflow lives in dockerizing.