Skip to content
Spring Boot sb getting-started 3 min read

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:run feel 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:

LayerContentsChanges
dependenciesRelease dependenciesRarely
spring-boot-loaderBoot loader classesRarely
snapshot-dependenciesSNAPSHOT dependenciesSometimes
applicationYour classes and resourcesEvery 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 Dockerfile entirely 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.

Last updated June 13, 2026
Was this helpful?