What Apache Tomcat Actually Does With Your Request (Startup, Request, Shutdown)
▶ Watch on YouTube & subscribe to The Stack Underflow
Every Spring MVC request passes through Tomcat before your controller runs. This video draws standalone Apache Tomcat 11 as one hierarchical state machine: how it starts, how one request travels from the socket to the servlet, and how it stops. This page walks the same path in writing.
The one-line version: standalone Tomcat binds its port in
init(), starts children before parents, and carries each request Coyote → CoyoteAdapter → Mapper → Valves → filters → servlet.
Last verified against the Apache Tomcat 11.0 documentation (version 11.0.26) and the 11.0.x source: 1 October 2026. Scope: Tomcat 11, standalone, default NIO HTTP connector, one Host and one web app. Clustering, AJP, HTTP/2 and JSP compilation are out of scope.
Six memory pegs
From the Tomcat words primer, where Tomcat is a hotel. These are analogies, memory aids only.
| Word | Memory peg | What it actually is |
|---|---|---|
| Server | The whole building | The entire Catalina servlet container |
| Service | A wing of the building | One or more Connectors tied to exactly one Engine |
| Connector | The entrance | Listens on one TCP port with one protocol |
| Context | One tenant business | One web application, chosen by its context path |
| Acceptor / Poller | The doorman / the concierge | Accepts new connections / watches open ones with one NIO selector |
| Lifecycle states | The opening and closing procedure | The twelve LifecycleState values every component follows |
Who owns whom
In standalone Tomcat, Tomcat owns the process and your web apps are deployed into it. In a Spring Boot jar it is the other way round: Spring Boot creates and owns an embedded Tomcat. That one difference changes when the port is bound (below).
Chapter 1 · Bootstrap
catalina.sh start (or running in the foreground, as container images do) launches org.apache.catalina.startup.Bootstrap. Its main() runs before any Tomcat component exists, and has two jobs:
- Build class loaders. By default only the Common loader, over Tomcat’s
libfolder, is real; the Server and Shared loaders are not defined and fall back to Common. - Load Catalina through that loader, set it to await a shutdown command, and call
load(), thenstart().
Chapter 2 · load(): server.xml, init, the ports
Catalina.load() parses conf/server.xml into Java objects (Server, Service, Connectors, Engine, Hosts), then calls init() down the tree. Each component goes NEW → INITIALIZING → INITIALIZED.
The detail that explains a classic failure: the connector’s bindOnInit attribute defaults to true. Tomcat’s docs: “If true, it is bound when the connector is initiated … If false, the socket will be bound when the connector is started.” So in standalone Tomcat the port is bound during init(), before any web app deploys. If port 8080 is taken, init fails, Catalina turns that into an error, and Bootstrap exits with status 1.
If server.xml cannot be parsed, there is no Server to start, and Bootstrap also exits with status 1.
Chapter 3 · start(): children first
start() runs down the same tree, with one rule worth memorising: a container starts its children, then its own pipeline, and only then enters STARTING itself. The parent waits in STARTING_PREP while its children run to STARTED, so the parent is the last to finish: Context, then Host, then Engine.
Inside the Service the order is explicit in StandardService.startInternal():
engine.start() // Hosts, Contexts, Wrappers
executors start
mapperListener.start() // tells the Mapper about every host, context, servlet
connectors start // only now can requests arrive
Requests can’t arrive before there is something to map them to.
One web application (Context /orders)
When Host localhost deploys a web app (the video uses orders.war at /orders), the Context:
- creates its own web-app class loader (
WEB-INF/classes, thenWEB-INF/lib, before Common; JVM classes and the Jakarta EE APIs always come from the parent), - reads
web.xml, web fragments and annotations, and builds the ServletContext, - calls listeners’
contextInitialized(), - calls every filter’s
init(), - initializes load-on-startup servlets in ascending order. Other servlets wait for their first request.
If a listener, filter or startup servlet fails, that web app is marked FAILED and isn’t available. The Host keeps deploying the others; one broken app doesn’t stop Tomcat.
When the connectors start their Poller and Acceptor threads, Catalina registers a JVM shutdown hook and the main thread waits for SHUTDOWN on port 8005.
Chapter 4 · One request, from socket to servlet
The connection. The Acceptor accepts connections until maxConnections (default 8192) are open. At that limit, new connections wait in the operating system’s queue, sized by acceptCount (default 100; the docs note the OS may use a different size). When that queue is full too, new connections may be refused or time out. An accepted connection waits in the Poller until data arrives, then is handed to a worker thread, at most maxThreads (default 200).
The request, on the worker thread:
| Step | What happens |
|---|---|
Http11Processor (Coyote) | Parses the request line and headers into a low-level Coyote Request |
CoyoteAdapter.service() | The bridge into Catalina: wraps it in a Catalina request, which your code sees as HttpServletRequest |
| Mapper | Host from the Host header (falling back to the default host), Context by longest context-path prefix, Wrapper from the servlet mappings. No matching Context: Tomcat answers 404 itself |
| Engine, Host, Context pipelines | Each container’s Valves run in order; the basic valves StandardEngineValve, StandardHostValve, StandardContextValve each pass it down a level |
StandardWrapperValve | Allocates the servlet instance (loading and calling init() once, if this is its first request) and builds the filter chain |
ApplicationFilterChain | Runs each filter in order. A filter can answer by itself (for example a security filter returning 401) and the servlet never runs. With Spring Security, this is where its filter chain runs |
servlet.service() | In Spring MVC, this servlet is DispatcherServlet, and Spring takes over |
The response goes back out through the filters and Coyote, and the worker thread returns to the pool. If the connection can be kept alive it returns to the Poller; Tomcat closes it after maxKeepAliveRequests (default 100) or after keepAliveTimeout of idle time.
Valves vs filters: Valves are a Tomcat mechanism (the access log valve is one); filters are part of the Servlet API and live in your app.
Chapter 5 · Graceful stop
SHUTDOWN on port 8005 (what catalina.sh stop sends) or the JVM shutdown hook (for example on SIGTERM) starts Catalina.stop(). The Service stops in a careful order:
- Connectors pause, so no new requests are processed.
- The Engine stops its Hosts and Contexts. Before a servlet’s
destroy()runs, its Wrapper waits up tounloadDelay(default 2000 ms) for requests still in progress. - Each Context destroys its filters, then calls listeners’
contextDestroyed(): the reverse of startup. - Connectors stop and close their sockets; executor threads end.
- The Server goes DESTROYING → DESTROYED and the JVM exits.
Chapter 6 · The Lifecycle
Every component, from the Server down to each Wrapper, follows the same twelve-state machine. Apart from FAILED, it is one line: NEW; INITIALIZING, INITIALIZED; STARTING_PREP, STARTING, STARTED; STOPPING_PREP, STOPPING, STOPPED; DESTROYING, DESTROYED.
Two rules from the Lifecycle Javadoc: “Any state can transition to FAILED”, and FAILED is not a dead end. Calling stop() on a failed component fires the stop events but moves it directly from FAILED to STOPPING, bypassing STOPPING_PREP, and then to STOPPED, from where it can be destroyed or started again.
Pause & Prove
1. Port 8080 is already taken. Does startup fail in init() or start()? And in Spring Boot’s embedded Tomcat?
- Standalone fails in
init(). ✓bindOnInitdefaults totrue, so the connector binds during init; init fails and Bootstrap exits with status 1. No web app ever deploys. - Standalone fails in
start(). Tempting, because “start” sounds like “open the port”, but that is only true whenbindOnInitisfalse. - Spring Boot fails in
start(). ✓ Spring Boot setsbindOnInit=falseand removes the connectors until its web server starts, so the bind, and the failure, happen atWebServer.start()infinishRefresh(), after your beans are created. - Both fail when the first request arrives. No: the port is bound during startup in both cases.
2. Inside one Tomcat web app, what starts first? (Community poll)
- Filters. Second: each filter’s
init()runs after the listeners. - Load-on-startup servlets. Third, in ascending order of their values.
- ServletContextListeners. ✓
contextInitialized()runs first. - The connector. Connectors start after the whole Engine, and therefore after every web app.
3. Peg drill
Say the peg for each: the Server, a Connector, a Context, the Acceptor and the Poller. Answers: the whole building; the entrance; one tenant business; the doorman and the concierge.
Related Shorts
- Tomcat’s port is taken: why no web app ever deploys
- One Tomcat web app fails to start. What happens to the others?
Before / after this video
- Before: Tomcat words: Catalina, Coyote, Connector, Context, Valve, Poller and Backend words: port, socket, reverse proxy, JVM, pod, SIGTERM
- Earlier in the series: The Spring Boot lifecycle, from java -jar to graceful shutdown, where Spring Boot owns an embedded Tomcat
- After: What NGINX does before your app sees a request (502 vs 504), which often sees the request before Tomcat does
Sources
Checked on 1 October 2026 (Tomcat docs version 11.0.26), plus the video’s own verified sources:
- HTTP Connector:
bindOnInitdefault and meaning;maxThreads,maxConnections,acceptCount,maxKeepAliveRequests,keepAliveTimeoutdefaults; NIO acceptor and poller - Lifecycle (Javadoc): the twelve states, valid transitions, FAILED behaviour
- Request processing: endpoint → protocol → Coyote adapter
- The Valve component: Valves in the Engine, Host or Context pipeline
- The Context container: longest-prefix mapping,
unloadDelaydefault 2000 ms - The Host container and the Engine container: deploy on startup,
defaultHost - The Server component: shutdown port and command
- Class loader how-to: Common loader, Server/Shared not defined by default, web-app search order
- StandardService.java and ContainerBase.java (11.0.x): Engine → executors → MapperListener → connectors; children, then pipeline, then STARTING
- Jakarta Servlet 6.1 specification: listeners, filters, load-on-startup order; checked for the video in September 2026
- Spring Boot’s
TomcatWebServer(bindOnInit=false, connectors added at start): checked for the video in September 2026
Change notes
- 1 Oct 2026: first published. Added from the HTTP Connector page: the operating system may use a different queue size than
acceptCount.
Not affiliated with or endorsed by the Apache Software Foundation or VMware Broadcom (Spring). Apache Tomcat is a trademark of the ASF. Found a mistake? Tell us in the video’s comments and we’ll correct this page.
Found this useful? The deep version lives on YouTube — new breakdowns of how AI dev tools actually work, weekly.
Subscribe on YouTube →Prefer email? Get the free newsletter: one failure, traced step by step, about once a week.