skip to content

Web Apps, Contexts & Classloading

This is the standalone deployment side of Tomcat: dropping a WAR into webapps/, what a Context actually is, and why Tomcat's parent-last classloader hierarchy makes the same jar behave differently in WEB-INF/lib than in lib/. Interviewers ask it because ClassNotFound/NoSuchMethod surprises, leaked classloaders on redeploy and JNDI datasource wiring are the classic legacy-Tomcat war stories.

on this pageshow

questions

6

You copy analytics.war into a standalone Tomcat's webapps/ directory. What context path does the application end up on, how would you instead serve it at the root path, and what does the Host attribute autoDeploy do while Tomcat is running?

level: juniorimportance: must knowfreq 68%

answer

  1. file name decides the path
  2. one reserved name means root
  3. hash characters carry meaning
  4. a scan thread watches appBase

basics

~20 s

Tomcat derives the context path from the WAR's file name, so analytics.war deploys at /analytics. Name the file ROOT.war to serve it at the root path. With autoDeploy enabled, Tomcat scans webapps/ while running and deploys or redeploys changed files.

solid answer

~40 s

Tomcat's `<Host>` has an `appBase`, `webapps` by default, and every WAR or directory there becomes a deployed context whose path comes from the file name. `analytics.war` gives `/analytics`; the reserved name `ROOT.war` (or a `ROOT` directory) gives the empty root path; a `#` in the name encodes a nested path, so `shop#eu.war` becomes `/shop/eu`; and a double `##` marks a version for parallel deployment, as in `shop##002.war`. You do not set the path yourself — a `path` attribute in the application's own `META-INF/context.xml` is ignored, which surprises people. `deployOnStartup` deploys what is present when Tomcat boots; `autoDeploy` keeps a background scan running so a newly copied or updated WAR is picked up without a restart. `unpackWARs` decides whether the archive is expanded into a directory first.

code

bash · 5 lines
bash
# The file name in appBase determines the context path
cp analytics.war "$CATALINA_BASE/webapps/"            # -> /analytics
cp portal.war    "$CATALINA_BASE/webapps/ROOT.war"    # -> /
cp eu.war        "$CATALINA_BASE/webapps/shop#eu.war" # -> /shop/eu
cp shop.war      "$CATALINA_BASE/webapps/shop##002.war" # -> /shop, version 002

go deeper

for a junior

Be able to say that the WAR's file name becomes the context path, that ROOT.war is the root application, and that copying the file into webapps/ is the deployment step.

for a middle

Explain the naming grammar precisely — the # and ## encodings, why a path attribute inside the app is ignored — and distinguish deployOnStartup, autoDeploy and unpackWARs by what each one actually triggers.

for a senior

Show that you know the failure modes: a WAR picked up mid-copy, an exploded directory silently recreated on redeploy, getRealPath returning null when WARs are not unpacked, and why many production installs disable autoDeploy entirely.

for a principal

Own the release story: whether hot redeploy into a long-lived JVM is allowed at all, how parallel deployment versions interact with session draining and rollback, and who is permitted to write into appBase in the first place.

## The pieces involved A standalone Tomcat serves applications out of an *application base directory*, configured as the `appBase` attribute of the `<Host>` element in `conf/server.xml`. The stock configuration is: ```xml <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> </Host> ``` A relative `appBase` is resolved against `$CATALINA_BASE`, the per-instance directory (which equals `$CATALINA_HOME` unless you split them). Anything Tomcat deploys becomes a **Context**: one web application, with its own class loader, its own JNDI naming environment, its own session manager, and one URL prefix — the *context path* — under which every one of its servlets is reachable. ## How the file name becomes the context path Tomcat does not ask the application what path it wants. It derives the path from the name of the WAR file or the directory: - `analytics.war` (or an `analytics/` directory) becomes context path `/analytics`. - `ROOT.war` or a `ROOT/` directory is the reserved spelling for the **empty** context path, i.e. the application answers `https://host/` directly. This is the standard way to put one application at the site root; nothing else does it. - `#` in the file name is decoded as `/`, so `shop#eu.war` deploys at `/shop/eu`. That is how you get a multi-segment path without nesting directories. - `##` introduces a **version** for parallel deployment: `shop##001.war` and `shop##002.war` are two versions of the same `/shop` context. Tomcat routes existing sessions to the old version and new sessions to the newest one, so you can retire the old WAR once its sessions drain. A common misconception is that you can write `path="/analytics"` in the application's `META-INF/context.xml`. Tomcat ignores it — the `path` attribute is only legal when a `<Context>` is nested directly in `server.xml`, which the documentation discourages because such a context cannot be reloaded without restarting Tomcat. ## deployOnStartup, autoDeploy, unpackWARs Three `<Host>` attributes govern the mechanics: - **`deployOnStartup`** (default `true`): at boot, Tomcat deploys everything it finds — context descriptors in `conf/<engine>/<host>/`, then directories, then WAR files. - **`autoDeploy`** (default `true`): a background thread rescans `appBase` periodically. Dropping a new WAR in deploys it; overwriting an existing WAR with a newer timestamp triggers an *undeploy and redeploy* of that context; deleting the WAR undeploys it. This is what makes `cp app.war webapps/` a deployment procedure, and it is also why a half-copied WAR can be picked up mid-copy — copy to a temporary name and rename, or use the Manager application's upload instead. - **`unpackWARs`** (default `true`): the archive is expanded into a directory next to it before it runs. Running unexpanded (`false`) saves the disk copy but makes `ServletContext.getRealPath()` return `null`, which breaks applications that expect a real file system path. ## What that means in practice ```bash cp analytics.war "$CATALINA_BASE/webapps/" # -> /analytics cp portal.war "$CATALINA_BASE/webapps/ROOT.war" # -> / ``` The copy is the whole deployment. Tomcat expands `analytics.war` into `webapps/analytics/`, creates the context, and starts it; failures appear in `logs/catalina.out` and in the context's own log, not on the console of the copy command. Because the exploded directory is *derived*, editing files inside it is temporary: the next redeploy deletes and recreates it. In production, teams often turn `autoDeploy` off and restart the JVM on each release instead, because repeated hot redeploys of the same JVM are the classic source of class-loader leaks. The naming rules stay the same either way. ## Related surfaces The Manager application (`/manager/html` and its text API) deploys, undeploys, reloads and lists contexts over HTTP using exactly the same naming rules, and the Host Manager application creates virtual hosts. Both are ordinary web applications shipped with Tomcat, guarded by roles in `conf/tomcat-users.xml`, and both are routinely removed from internet-facing installations.

  • What actually happens on disk when autoDeploy notices that an already-deployed WAR has a newer timestamp?
    Tomcat undeploys the running context, deletes the exploded directory under `appBase` and the context's directory under `work/`, then expands the new archive and starts a fresh context with a new class loader. Anything you edited inside the exploded directory is gone, and in-memory state such as sessions belonging to the old context is discarded unless it was persisted.
  • Why would you set unpackWARs to false, and what tends to break?
    Running straight from the archive avoids a second copy on disk and keeps the deployed unit byte-identical to what was built. The cost is that `ServletContext.getRealPath()` returns `null`, so any code that expects a real file — writing uploads next to the application, reading a template by absolute path, some older libraries scanning the file system — fails. Applications that only use `getResourceAsStream()` are unaffected.
  • What does the `##` version suffix buy you over simply overwriting the WAR?
    Parallel deployment. Both versions run at the same context path at once: requests carrying a session that belongs to the older version keep going there, while new sessions land on the highest version. You undeploy the old WAR when its sessions have drained, so a release does not throw active users out mid-session — at the cost of two copies of the application in one JVM.

The webapps directory works like a drop folder rather than a registry: the file's name is the whole deployment request, so renaming the file is how you choose the URL.

saying these in an interview costs you the question

  • Thinking the app declares its own path in context.xml
  • Believing you must restart Tomcat to deploy a WAR
  • Calling the root app index.war or default.war
  • Assuming edits to the exploded directory survive redeploy
  • Confusing autoDeploy with reloadable class watching

context

open as a page

In a standalone Tomcat, the same library jar sits both in $CATALINA_BASE/lib and in a web application's WEB-INF/lib. Which copy does the application's code load, and what goes wrong when instances of that library's classes have to pass between Tomcat's own code and the application?

level: middleimportance: must knowfreq 60%

basics

~20 s

The application's own copy wins: Tomcat's web application class loader searches WEB-INF/classes and WEB-INF/lib before the shared loader that owns $CATALINA_BASE/lib. Both copies then exist as distinct classes, so objects crossing between container and application fail with ClassCastException or NoClassDefFoundError.

open as a page

You edited a Tomcat application's settings in webapps/myapp/META-INF/context.xml, and the change vanished the next time the WAR was deployed. Where does Tomcat look for a Context descriptor, which location survives redeployment, and what do the Host attributes deployXML and copyXML control?

level: middleimportance: should knowfreq 42%

basics

~20 s

Redeploying a WAR deletes and recreates the exploded directory, taking your edit with it. The descriptor that survives is conf/<engine>/<host>/<appname>.xml, which also wins over the copy packaged in the WAR. deployXML decides whether packaged descriptors are honoured at all; copyXML copies one out at deployment.

open as a page

How do you expose a container-managed JDBC DataSource to a Tomcat web application using a <Resource> element, how does application code look it up, and where must the JDBC driver jar be placed?

level: middleimportance: should knowfreq 50%

basics

~20 s

Declare a <Resource> of type javax.sql.DataSource in the application's Context descriptor; Tomcat builds a pool and binds it in that application's private JNDI tree. Code looks it up at java:comp/env/<name>. The driver jar must sit in $CATALINA_BASE/lib, not in WEB-INF/lib.

open as a page

A Tomcat instance that is redeployed several times a day eventually dies with java.lang.OutOfMemoryError: Metaspace, and a full restart buys another few days. What happens on each redeploy to cause this, and how would you find and fix the cause?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Each deployment gets a fresh web application class loader that should become garbage when the context stops. One reference from outside the application — an unstopped thread, a ThreadLocal, a registered JDBC driver — pins it, so every class it loaded stays in metaspace and each redeploy adds another full copy.

open as a page

Users of an application on a standalone Tomcat are logged out whenever the server restarts or the WAR is redeployed. How does Tomcat's default session manager treat sessions across a stop and start, and what makes them disappear anyway?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

Tomcat's StandardManager keeps sessions in memory and, on a graceful stop, serializes the active ones to a file under the work directory, reloading them at the next start. A crash, a non-serializable attribute, a redeploy that clears the work directory, or a disabled pathname all defeat it.

open as a page