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?
answer
- the server ships inside the jar
- no server.xml, no webapps directory
- one property picks the listener port
- zero is a socket idiom, not a switch
basics
~20 sspring-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 sThere 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 linesserver.port=0
server.address=0.0.0.0
server.servlet.context-path=/apigo deeper
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.
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.
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.
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