Deploying WAR and webapps

Introduction

A Tomcat install is useful only when applications are deployed. This chapter covers the WAR layout, four deploy methods—from copying a file into webapps/ to external docBaseContext path rules, ROOT mapping, and clean undeploy. Chapter 08 builds the WAR with Maven; here the focus is getting any WAR onto Tomcat and verifying it runs.

Prerequisites

WAR Structure Recap

A WAR (Web Application Archive) is a ZIP with a fixed layout:

text
myapp.war
├── META-INF/
│   ├── MANIFEST.MF
│   └── context.xml          ← optional
├── WEB-INF/
│   ├── web.xml              ← optional (Servlet 3+)
│   ├── classes/             ← .class files (com/example/…)
│   └── lib/                 ← application JARs
├── index.html               ← public static files
└── css/, js/, …
PathURL access
/index.html (WAR root)http://host:8080/myapp/index.html
WEB-INF/web.xmlNot browsable
WEB-INF/classes/Not browsable

Code explanation:

  • Context path /myapp comes from the WAR name myapp.war by default
  • Maven builds WARs to target/myapp.war after mvn package

Inspect a WAR without deploying:

bash
jar tf target/myapp.war | head -20
unzip -l target/myapp.war

Context Path and URL Mapping

Deploy nameDefault context pathExample URL
hello.war/hellohttp://localhost:8080/hello/
hello/ (exploded dir)/hellosame
ROOT.war or ROOT// (site root)http://localhost:8080/
conf/Catalina/localhost/api.xml/apihttp://localhost:8080/api/

Code explanation:

  • Context path is the first URL segment after host
  • Servlet @WebServlet("/users") inside hello.war/hello/users

Override path in META-INF/context.xml (use carefully):

xml
<Context path="/custom" />

Prefer matching WAR name and URL expectations to avoid confusion.

Method 1: Copy WAR to webapps/ (Auto-Deploy)

Simplest path when autoDeploy="true" on the Host (default).

bash
cp target/myapp.war $CATALINA_HOME/webapps/
# wait a few seconds
curl -I http://127.0.0.1:8080/myapp/

Code explanation:

  • Tomcat detects the new file, expands to webapps/myapp/ if unpackWARs="true"
  • Logs: logs/catalina.*.log and logs/localhost.*.log

Upgrade:

bash
$CATALINA_HOME/bin/shutdown.sh
rm -rf $CATALINA_HOME/webapps/myapp $CATALINA_HOME/webapps/myapp.war
cp target/myapp-2.war $CATALINA_HOME/webapps/myapp.war
$CATALINA_HOME/bin/startup.sh

Tip

Stop Before Replace on Busy Systems

Hot-replacing WAR while running can work with autoDeploy but risks partial state. Shutdown → replace → start is safer for production.

Method 2: Exploded Directory

Unpack manually for inspection or quick static edits (learning only):

bash
mkdir -p $CATALINA_HOME/webapps/myapp
cd $CATALINA_HOME/webapps/myapp
jar xf /path/to/myapp.war

Or let Tomcat unpack by dropping myapp.war once, then edit webapps/myapp/.

Code explanation:

  • Tomcat serves the directory directly—no separate WAR file required if folder exists
  • Do not treat exploded webapps/ as your Git project—always rebuild from source and redeploy WAR for real work

Remove:

bash
rm -rf $CATALINA_HOME/webapps/myapp
# also remove myapp.war if present

Method 3: Manager Application

Tomcat ships Manager at /manager/html (if not removed).

Enable access

  1. Edit conf/tomcat-users.xml — see web.xml and context
  2. Restart Tomcat
  3. Open http://127.0.0.1:8080/manager/html
  4. Log in with manager-gui role user

From the GUI:

  • WAR file to deploy — upload local WAR
  • Deploy — specify context path and optional XML
  • Undeploy — remove application

Text API (scripting):

bash
curl -u admin:password \
  --upload-file target/myapp.war \
  "http://127.0.0.1:8080/manager/text/deploy?path=/myapp&update=true"

Code explanation:

  • manager-script role required for text API
  • Restrict Manager to localhost or admin VPN—never expose weak credentials on the public internet

List deployed apps:

bash
curl -u admin:password http://127.0.0.1:8080/manager/text/list

Method 4: External docBase (Production-Friendly)

Keep WARs outside CATALINA_HOME/webapps/ for cleaner upgrades and permissions.

Create conf/Catalina/localhost/myapp.xml:

xml
<?xml version="1.0" encoding="UTF-8"?>
<Context docBase="/opt/apps/myapp.war"
         reloadable="false"
         unpackWARs="true" />

Deploy workflow:

bash
sudo mkdir -p /opt/apps
sudo cp target/myapp.war /opt/apps/myapp.war
sudo chown tomcat:tomcat /opt/apps/myapp.war
# restart Tomcat or touch context if configured
sudo systemctl restart tomcat
curl -I http://127.0.0.1:8080/myapp/

Code explanation:

  • File name myapp.xml → context /myapp
  • docBase — absolute path to WAR or directory
  • Upgrade: replace /opt/apps/myapp.war, restart Tomcat—Tomcat install dir untouched

ROOT at site root with external WAR

conf/Catalina/localhost/ROOT.xml:

xml
<Context docBase="/opt/apps/frontend.war" reloadable="false" />

Serves app at http://localhost:8080/ (no /myapp prefix).

Deploy Method Comparison

MethodProsCons
webapps/ copyFastest for learningMixes apps with Tomcat tree
Exploded dirEasy to inspect filesEasy to drift from source control
ManagerUpload without SSHSecurity risk if exposed
External docBaseClean ops, CI-friendlyExtra path + fragment file

Undeploy

MethodAction
webapps/Stop Tomcat; delete myapp.war and myapp/ directory
ManagerUndeploy button or text /undeploy?path=/myapp
localhost fragmentRemove myapp.xml, delete WAR at docBase, restart

Verify removal:

bash
curl -I http://127.0.0.1:8080/myapp/    # expect 404
ls $CATALINA_HOME/webapps/

Stale work/ cache (optional cleanup after undeploy):

bash
rm -rf $CATALINA_HOME/work/Catalina/localhost/myapp

Deployment Checklist

After every deploy:

bash
# 1. Log errors
tail -40 $CATALINA_HOME/logs/localhost.$(date +%Y-%m-%d).log
 
# 2. HTTP status
curl -I http://127.0.0.1:8080/myapp/
 
# 3. Context list (if Manager enabled)
curl -u admin:password http://127.0.0.1:8080/manager/text/list
CheckPass
No SEVERE stack traces in logClean startup lines for Context /myapp
curl -I200, 302, or expected auth redirect—not 404
Correct contextURL prefix matches design

Common Deploy Failures

SymptomCauseFix
404 on all app URLsWrong context path; deploy not finishedCheck WAR name vs URL; read logs
ClassNotFoundException: jakarta.servlet…Tomcat 9 vs 10 mismatchTomcat 10 for Jakarta apps
IllegalArgumentException: Invalid docBaseBad path in localhost XMLAbsolute path; file exists; permissions
App starts then stopsMissing dependency in WEB-INF/libRebuild WAR; check mvn dependency:tree
myapp.war not expandingunpackWARs="false" or disk fullCheck Host settings and disk space
Old version still servedBrowser cache or duplicate deployHard refresh; remove old webapps/myapp/

autoDeploy and Production

From server.xml and Connectors:

xml
<Host name="localhost" appBase="webapps"
      unpackWARs="true" autoDeploy="false">

Code explanation:

  • autoDeploy="false" — Tomcat ignores new files in webapps/ until restart or Manager deploy
  • CI pipeline: copy WAR to /opt/apps/, systemctl restart tomcat

Package-Managed Tomcat Paths

Debian/Ubuntu tomcat10:

bash
sudo cp target/myapp.war /var/lib/tomcat10/webapps/
sudo systemctl restart tomcat10

External fragment may live under /etc/tomcat10/Catalina/localhost/—check your distro layout with:

bash
dpkg -L tomcat10 | grep Catalina

Relationship to Spring Boot

ArtifactDeploy to standalone Tomcat
java -jar app.jarNo — embedded server, not a WAR
app.war built with SpringBootServletInitializerYes — same deploy methods as any WAR

See Spring Boot WAR on Tomcat.

Post-Chapter Checklist

  • Deployed a WAR to webapps/ and verified with curl
  • Understand context path from WAR file name
  • Know how ROOT.war maps to /
  • Can write a conf/Catalina/localhost/*.xml fragment with external docBase
  • Know safe undeploy steps (remove WAR + exploded dir)

What Comes Next

ChapterTopic
08 — First Servlet applicationBuild WAR with Maven from scratch
12 — Nginx reverse proxy TomcatPublic URL → Tomcat
16 — Project: Maven WAR deployEnd-to-end exercise

FAQ

Can I rename the WAR after deploy?

Not cleanly—context path is tied to the name. Undeploy, rename myapp.war, redeploy, or set path in localhost XML.

Deploy without restart?

autoDeploy may pick up new WARs; production should restart or use Manager with a documented procedure.

One Tomcat, how many apps?

Many—each WAR is a Context. Watch memory and thread limits; see JVM tuning.

webapps/myapp.war and webapps/myapp/ both exist?

Tomcat prefers the exploded directory if both exist in some states—remove both when redeploying to avoid stale classes.

Manager 403?

User lacks role, or remote access blocked by RemoteAddrValve in Manager context—use localhost or configure allow list.

WAR vs JAR?

WAR for Servlet containers (Tomcat). JAR with main for Spring Boot executable—different deploy path.