skip to content

Tomcat

Tomcat is the servlet container most JVM web apps actually run inside, whether you deploy a WAR onto a standalone install or ship it embedded in a Spring Boot fat jar. Interviewers ask about it because the connector thread pool, the context/classloader model and the embedded-server knobs are where JVM HTTP traffic really succeeds or stalls.

on this pageshow

explore

questions

18

A Spring Boot application built with spring-boot-starter-web starts with `java -jar app.jar` on a host where no Tomcat is installed, and logs "Tomcat started on port 8080". What is actually serving HTTP, and what does setting `server.port=0` do?

level: juniorimportance: must knowfreq 72%

answer

  1. the server ships inside the jar
  2. no server.xml, no webapps directory
  3. one property picks the listener port
  4. zero is a socket idiom, not a switch

basics

~20 s

spring-boot-starter-web pulls Tomcat in as an ordinary library, so the application starts a real Tomcat inside its own JVM process on port 8080. Setting server.port=0 makes it bind a random free port chosen by the operating system.

solid answer

~40 s

There is a real Tomcat there — it is just embedded rather than installed. `spring-boot-starter-web` depends on `spring-boot-starter-tomcat`, which brings the `tomcat-embed-core` jars onto the classpath; at startup Spring Boot sees a servlet stack, builds a web server factory, and starts Tomcat in-process. The host needs only a JRE: no distribution, no `server.xml`, no `webapps` directory, and the application owns the server's lifecycle instead of the other way round. `server.port` chooses the listener port and defaults to 8080; it can be set in `application.properties`, as the `SERVER_PORT` environment variable, or as `--server.port=9000` on the command line, so the same artifact runs anywhere. `server.port=0` asks the OS for an ephemeral free port — the normal choice for tests, where `@SpringBootTest(webEnvironment = RANDOM_PORT)` plus `@LocalServerPort` reads back the port that was actually assigned.

code

properties · 3 lines
properties
server.port=0
server.address=0.0.0.0
server.servlet.context-path=/api

go deeper

for a junior

Be able to say plainly that the server is a library inside the jar, that the default port is 8080, and that server.port can be overridden by environment variable or command line without rebuilding.

for a middle

Explain the startup sequence — classpath detection, web server factory, connector bound in-process — and why server.port=0 plus @LocalServerPort is the right pattern for integration tests.

for a senior

Show that you still treat the embedded container as a real server: it has a bounded thread pool and an accept queue that need sizing, and its configuration surface simply moved from XML to properties.

for a principal

Own the packaging decision for a fleet: self-contained jars give per-service isolation and independent upgrades, while a shared standalone container centralises patching at the cost of coupling every deployment to one server's lifecycle.

## Embedded rather than deployed The classic model was: install a Tomcat distribution on a server, drop a WAR into `webapps/`, and let Tomcat's classloader find and start your application. Spring Boot inverts that relationship. Tomcat's jars — `tomcat-embed-core`, `tomcat-embed-el`, `tomcat-embed-websocket` — are pulled in as ordinary dependencies by `spring-boot-starter-tomcat`, which `spring-boot-starter-web` includes transitively. Your `main()` method runs first, Spring Boot detects a servlet web application on the classpath, creates a servlet web server factory, and that factory constructs a Tomcat instance, attaches a connector, and starts it inside the same JVM. The practical consequences are large. The deployable unit is one self-contained jar. The host needs only a JRE. There is no shared server whose version, `server.xml`, or global libraries are owned by someone else, and no possibility of one badly behaved application taking down three others deployed beside it. ## What is still true Embedded Tomcat is not a cut-down imitation — it is the same Tomcat code, with the same connector, the same bounded pool of request-processing threads, and the same behaviour under load. That is exactly why interviewers ask this: engineers who have only ever used Boot sometimes talk as if the servlet container disappeared. It did not. It still has a maximum thread count (`server.tomcat.threads.max`, default 200), it still has an accept queue, and it still needs to be sized for the workload. What changed is the configuration surface: settings that used to be XML attributes are now `server.*` and `server.tomcat.*` properties, with a programmatic escape hatch for anything not exposed. ## server.port `server.port` selects the port the embedded connector binds. The default is 8080. Because it is an ordinary Spring `Environment` property, it can come from `application.properties` or `application.yaml`, a profile-specific file, the `SERVER_PORT` environment variable, a JVM system property, or a `--server.port=9000` command-line argument — with command line and environment overriding the packaged file. The same immutable artifact therefore runs on a different port in a different environment with no rebuild, which is the whole point of externalized configuration. ```properties server.port=9000 server.address=127.0.0.1 server.servlet.context-path=/api ``` `server.address` restricts which interface is bound — useful when the process should only be reachable from a proxy on the same host. `server.servlet.context-path` prefixes every mapping, which is how you reproduce the old "application deployed under /api" arrangement without a WAR. ## server.port=0 Setting the port to `0` is a standard socket idiom: the OS picks any free ephemeral port at bind time. Spring Boot logs the port it actually got, and in tests the value is injected: ```java @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class ApiIT { @LocalServerPort int port; } ``` This is what makes parallel or CI test runs safe — two builds on the same machine cannot collide on a hardcoded 8080. It is also occasionally used by services that register their own address with a discovery registry after startup, since the registered port is discovered rather than fixed. It is the wrong choice anywhere the port must be predictable: a firewall rule, a proxy upstream, or a container port mapping all need a known number. ## Jar or war Boot can still produce a WAR for a standalone Tomcat: set the packaging to `war`, mark `spring-boot-starter-tomcat` as `provided` so the embedded copy is not bundled twice, and have the main class extend `SpringBootServletInitializer` so an external container can bootstrap the application context. The result is still executable as a jar. This exists mostly for organisations with a mandated shared application server; the default and overwhelmingly common path is the executable jar with the container inside it. ## The startup log line "Tomcat started on port 8080 (http)" is printed after the connector has successfully bound. A failure here — `Web server failed to start. Port 8080 was already in use.` — is a bind failure of the embedded connector in your own process, not a problem with an installed server, and the fix is to change `server.port` or stop whatever already holds the socket.

  • How would you take that same Spring Boot codebase and deploy it into a standalone Tomcat instead?
    Change packaging to `war`, mark `spring-boot-starter-tomcat` as `provided` so the embedded jars are not shipped inside the war, and extend `SpringBootServletInitializer` in the main class so the external container can bootstrap the application context. The artifact stays executable as a jar too. It is a legitimate path for organisations mandating a shared application server, but you give up self-contained deployment.
  • Where do you set the port when the application runs in a container image you cannot rebuild?
    As the `SERVER_PORT` environment variable, or a `--server.port=` argument appended to the command. Spring Boot's property resolution puts both above the packaged `application.properties`, which is why the same image runs on any port. Nothing in the jar has to change.
  • The application fails at startup with "Port 8080 was already in use". What is happening?
    The embedded connector could not bind its socket, because another process — often a previous run of the same application that never exited — already holds the port. It is a failure inside your own JVM, not a problem with any installed server. Kill the holder, or set a different `server.port`.

saying these in an interview costs you the question

  • Thinks Tomcat must be installed on the host machine
  • Believes the fat jar is deployed into a webapps directory
  • Says server.port=0 disables the web server
  • Claims embedded Tomcat is a different, lighter product than standalone Tomcat
  • Assumes changing the port requires rebuilding the artifact

context

open as a page

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%

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.

open as a page

In a Tomcat server.xml <Connector>, what do maxThreads, maxConnections and acceptCount each limit, and what happens to an incoming connection as each of those limits is reached in turn?

level: middleimportance: must knowfreq 78%

basics

~20 s

maxThreads caps how many requests Tomcat processes at once, maxConnections caps how many sockets it holds open, and acceptCount is the OS accept-queue length used once maxConnections is full. Past all three, new connections are refused.

open as a page

A Spring Boot service on embedded Tomcat stops answering new requests under load while its CPU stays near idle. Which `server.tomcat.*` properties bound how many requests it can handle at once, and what happens to a request that arrives past each bound?

level: middleimportance: must knowfreq 66%

basics

~20 s

Three properties bound it: server.tomcat.max-connections (default 8192) caps accepted connections, server.tomcat.threads.max (default 200) caps requests processed at once, and server.tomcat.accept-count (default 100) sizes the OS backlog beyond that. Idle CPU means the 200 threads are blocked, not busy.

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

A Java service on Tomcat behind a reverse proxy starts returning gateway timeouts to users, yet Tomcat's own logs look quiet, CPU is near idle and the host has plenty of memory. How do you confirm the connector's thread pool is exhausted, and what do you do once you have?

level: seniorimportance: must knowfreq 57%

basics

~20 s

Take a thread dump and count the connector's exec threads: if all maxThreads are alive and blocked in the same downstream call, the pool is exhausted. Confirm with the ThreadPool MBean's currentThreadsBusy, then bound the downstream call rather than enlarging the pool.

open as a page

In a Tomcat server.xml <Connector>, what does the connectionTimeout attribute actually time out, and how does keepAliveTimeout differ from it?

level: juniorimportance: should knowfreq 45%

basics

~20 s

connectionTimeout is how long Tomcat waits, after accepting a connection, for the request line to arrive. keepAliveTimeout is how long it waits for the next request on an already-used connection, and defaults to the connectionTimeout value.

open as a page

You need to configure something on Spring Boot's embedded Tomcat that no `server.*` property exposes — for example an additional plain-HTTP connector alongside the main one. How do you do it in code, and how does that interact with the `server.*` properties already set?

level: middleimportance: should knowfreq 38%

basics

~10 s

Register a WebServerFactoryCustomizer<TomcatServletWebServerFactory> bean. It receives the factory before the server is built, so you can call addAdditionalTomcatConnectors or addConnectorCustomizers. It runs after the property-driven customizer, so code overrides server.* values.

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 Spring app on Tomcat sits behind a TLS-terminating reverse proxy. It logs the proxy's IP address as every user's address and builds http:// links even though users arrived over HTTPS. Which Tomcat-side configuration fixes the client IP and scheme, and how do you keep an attacker from spoofing it?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Enable Tomcat's RemoteIpValve so forwarded headers rewrite the request's remote address, scheme, port and secure flag; restrict trust with its internalProxies and trustedProxies patterns. A static alternative is the connector's scheme, secure, proxyName and proxyPort attributes.

open as a page

During rolling restarts, a Spring Boot service on embedded Tomcat returns errors for requests that were already in flight. What does `server.shutdown=graceful` change about how the embedded container stops, which property bounds it, and why is that setting alone often not enough?

level: seniorimportance: should knowfreq 50%

basics

~20 s

With server.shutdown=graceful the embedded container stops accepting new requests and lets in-flight ones finish, bounded by spring.lifecycle.timeout-per-shutdown-phase (default 30 seconds). It does not stop the load balancer routing new traffic, so a pre-shutdown delay is still needed.

open as a page

Setting `spring.threads.virtual.enabled=true` on a Spring Boot 3.2+ application running Java 21 changes how the embedded Tomcat handles requests. What changes, and which setting then bounds how many requests run at once?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Spring Boot gives embedded Tomcat a virtual-thread-per-task executor, so each request gets a fresh virtual thread instead of one from the fixed pool. server.tomcat.threads.max stops bounding concurrency; server.tomcat.max-connections and accept-count become the real admission limits.

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

On a Tomcat service, one endpoint spends nearly all of each request blocked on a slow third-party API and its exec threads are saturated. How do you decide between raising maxThreads, converting the endpoint to an async servlet, and giving it its own <Executor> or connector?

level: principalimportance: should knowfreq 36%

basics

~20 s

Decide by what the threads are waiting on and who else shares the pool. Async frees the exec thread during the wait and lifts a throughput ceiling; a separate Executor buys isolation; a bigger pool only helps when the dependency can absorb more concurrency and the threads themselves are cheap.

open as a page

A Tomcat <Connector> element's protocol attribute can be set to "HTTP/1.1", to org.apache.coyote.http11.Http11Nio2Protocol, or to the APR/native implementation. What do those choose between, and which would you deploy today?

level: middleimportance: nice to knowfreq 38%

basics

~20 s

They select Tomcat's socket-handling implementation. NIO uses Java non-blocking sockets with poller threads and is the default; NIO2 uses the JDK's asynchronous channel API; APR/native used the C Apache Portable Runtime and is gone in current Tomcat. Deploy NIO.

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

A team proposes replacing embedded Tomcat with Undertow or Jetty across a fleet of Spring Boot services, arguing it will improve throughput. How would you evaluate that proposal, and what does the swap actually cost?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

The swap is mechanically trivial — exclude the Tomcat starter, add the Jetty or Undertow one — but the container is rarely the bottleneck. The real cost is that every server.tomcat.* property and Tomcat-typed customizer silently stops applying, taking tuning with it.

open as a page