Spring Boot Tutorial
Packaging and Deployment: JAR, WAR and External Tomcat
Once your application works, you need to run it somewhere else: a server, a container platform, or a company's existing Tomcat installation. Spring Boot supports several ways to package and run an application, and choosing the right one affects startup, operations and upgrades.
This lesson covers every way to run a Spring Boot app during development, building an executable JAR and what is inside it, layered jars and CDS for faster startup, building a WAR for an external servlet container, deploying to external Tomcat 11, running as a Linux service, and the Spring Boot CLI.
Ways to Run During Development
You can run the main method from your IDE; use mvn spring-boot:run or ./gradlew bootRun; run the test-classpath variant with spring-boot:test-run (Testcontainers); or build and run the jar with java -jar. Pass arguments with -Dspring-boot.run.arguments="--server.port=9090" (Maven) or --args (Gradle).
The Executable JAR
mvn package produces a "fat" jar containing your classes (BOOT-INF/classes), all dependencies (BOOT-INF/lib) and a small launcher. It runs anywhere with a compatible JVM: java -jar app.jar. This is the default and recommended packaging — one artifact, embedded Tomcat, the same everywhere from laptop to production container.
Faster Startup: Extracting and CDS
For containers and production, extract the jar with java -Djarmode=tools -jar app.jar extract and run the extracted form; it starts faster and enables Class Data Sharing (CDS) or Java 24+ AOT caches, which can cut startup time significantly. Layered extraction (--layers) also produces efficient Docker image layers, so rebuilding after a code change only uploads your classes, not all dependencies.
Traditional WAR Deployment
If your organisation runs shared Tomcat servers, package a WAR: set <packaging>war</packaging>, extend SpringBootServletInitializer so the container can start Spring, and mark spring-boot-starter-tomcat as provided so the embedded Tomcat is not bundled. Spring Boot 4 requires a Servlet 6.1 container such as Tomcat 11 or Jetty 12.1. The resulting WAR is still executable with java -jar for local testing.
Running in Production
On a Linux server, run the jar as a systemd service with restart policies, a dedicated user and environment-based configuration. On Kubernetes or cloud platforms, build a container image (see the Docker and Buildpacks lessons). In every case, externalise configuration, send logs to stdout or a central system, and use Actuator health endpoints for monitoring.
Spring Boot CLI
The Spring Boot CLI (spring command, installed via SDKMAN or Homebrew) can generate new projects from the command line using Spring Initializr: spring init --dependencies=webmvc,data-jpa my-app. Older versions could also run Groovy scripts directly; that feature was removed, and the CLI is now mainly a project generator and password encoder utility.
Examples
Building and inspecting the executable jar
mvn clean package
java -jar target/shop-1.0.0.jar --spring.profiles.active=prod
# What is inside?
jar tf target/shop-1.0.0.jar | more
META-INF/MANIFEST.MF (Main-Class: org.springframework.boot.loader.launch.JarLauncher)
BOOT-INF/classes/com/webnest/shop/ShopApplication.class
BOOT-INF/classes/application.yml
BOOT-INF/lib/spring-webmvc-7.0.9.jar
BOOT-INF/lib/tomcat-embed-core-11.0.x.jar
BOOT-INF/lib/hibernate-core-7.x.jar
...
Extracting for faster startup with Class Data Sharing
# 1. Extract the jar into an efficient layout
java -Djarmode=tools -jar target/shop-1.0.0.jar extract --destination app
# 2. Training run: create a CDS archive, then exit
java -XX:ArchiveClassesAtExit=app/app.jsa -Dspring.context.exit=onRefresh -jar app/shop-1.0.0.jar
# 3. Production runs use the archive
java -XX:SharedArchiveFile=app/app.jsa -jar app/shop-1.0.0.jar
Without CDS: Started ShopApplication in 3.42 seconds
With CDS: Started ShopApplication in 2.05 seconds
Packaging a WAR for an external Tomcat 11
<!-- pom.xml -->
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope> <!-- the external Tomcat supplies the server -->
</dependency>
</dependencies>
// Application class
@SpringBootApplication
public class ShopApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(ShopApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class, args); // still runnable with java -jar
}
}
# Build and deploy
mvn clean package
copy target\shop.war C:\apache-tomcat-11.0\webapps\
C:\apache-tomcat-11.0\bin\startup.bat
Tomcat log: Deploying web application archive [C:\apache-tomcat-11.0\webapps\shop.war]
... Started ServletInitializer in 4.2 seconds
curl http://localhost:8080/shop/api/products -> 200 OK (context path = WAR file name)
Running as a Linux systemd service
# /etc/systemd/system/webnest-shop.service
[Unit]
Description=Webnest Shop
After=network.target
[Service]
User=webnest
WorkingDirectory=/opt/webnest-shop
EnvironmentFile=/etc/webnest-shop.env
ExecStart=/usr/bin/java -Xmx512m -jar /opt/webnest-shop/shop.jar
SuccessExitStatus=143
Restart=on-failure
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now webnest-shop
journalctl -u webnest-shop -f
● webnest-shop.service - Webnest Shop
Active: active (running) since Sun 2026-09-27 10:02:11 IST
Common Mistakes
- Deploying a Spring Boot 4 WAR to Tomcat 9 or 10 — it requires a Servlet 6.1 container such as Tomcat 11.
- Forgetting SpringBootServletInitializer, so the external container deploys the WAR but never starts Spring.
- Leaving the embedded Tomcat in compile scope for a WAR, bundling a second server into the archive.
- Running production with mvn spring-boot:run instead of a built artifact.
- Baking environment-specific configuration into the jar instead of supplying it at runtime.
Key Points to Remember
- Run in development with the IDE, spring-boot:run/bootRun, or java -jar.
- The executable fat jar with embedded Tomcat is the default, recommended packaging.
- Extract the jar and use CDS for faster startup and better container layers.
- For external servers, package a WAR with SpringBootServletInitializer and provided Tomcat; use Tomcat 11.
- In production run as a service or container with external configuration and health checks.
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.