The Embedded Server
The Embedded Server
One of the most visible differences between a traditional Java web application and a Spring Boot application is how you run it. In the traditional model you packaged your code as a WAR file and deployed it into a separately installed application server — Tomcat, JBoss, WebLogic. In Spring Boot the server ships inside your JAR. You run java -jar myapp.jar and Tomcat starts on its own. This lesson explains exactly how that works, why it matters, and how to configure and swap it.
What "Embedded" Means
An embedded server is an application server whose lifecycle is managed by your application code, not the other way around. Spring Boot pulls this off by including the server as an ordinary dependency. When your main() method calls SpringApplication.run(), the framework:
- Creates the Spring
ApplicationContext. - Detects the embedded-server dependency on the classpath.
- Programmatically instantiates and configures the server (e.g.,
org.apache.catalina.startup.Tomcat). - Registers your
DispatcherServletwith it. - Starts the server and binds it to a port.
The entire bootstrap happens inside the JVM process. When you kill the process the server stops too — no separate shutdown script, no undeploy step.
The Three Built-in Server Choices
Spring Boot ships auto-configuration for three embedded servers. You choose one by controlling which starter is on your classpath:
- Tomcat (default) — mature, ubiquitous, battle-tested. Pulled in automatically by
spring-boot-starter-web. - Jetty — lightweight, historically strong at handling long-lived connections (WebSocket, HTTP/2 streaming).
- Undertow — high-throughput, non-blocking I/O core, low memory footprint per connection. Popular for reactive workloads even in the servlet stack.
To switch from Tomcat to Undertow you exclude the Tomcat starter and add the Undertow starter in your pom.xml:
No code changes are needed — Spring Boot detects the new server on the classpath and wires it up identically. This is auto-configuration doing its job.
Configuring the Server via application.properties
The most common server settings live under the server.* namespace in application.properties (or application.yml):
server.port=0 tells the OS to assign any available port. This is extremely useful in integration tests — each test run gets a unique port, eliminating port-conflict flakiness. Inject the actual chosen port in a test with @LocalServerPort.
Programmatic Server Customisation
Properties cover 80% of use cases. For the rest — custom connectors, custom error pages, Gzip compression tuned beyond what properties expose — you implement a WebServerFactoryCustomizer. The generic version works across all three servers; server-specific sub-interfaces give you access to vendor APIs:
application.properties for standard settings. Reserve WebServerFactoryCustomizer for things that genuinely have no property equivalent. Customizers that use the server-specific interface (e.g. TomcatServletWebServerFactory) break if you later swap servers; the generic ConfigurableServletWebServerFactory is portable.
Graceful Shutdown
By default, when Spring Boot receives a shutdown signal (SIGTERM) it stops the server immediately — in-flight requests are cut off. Since Spring Boot 2.3 you can enable graceful shutdown, which allows active requests to complete before the process exits:
With server.shutdown=graceful the server stops accepting new connections the moment the signal arrives but continues processing requests already in progress, up to the timeout-per-shutdown-phase limit. This is essential for zero-downtime rolling deployments in Kubernetes or behind a load balancer.
The Executable JAR and How It Works
When you run mvn package (or ./gradlew bootJar), the Spring Boot build plugin creates a fat JAR — a single archive that contains your compiled classes, all dependency JARs (including the server JAR), and a custom launcher. The launcher understands nested JARs (the standard Java class loader does not), loads them all, and then invokes your @SpringBootApplication class's main() method.
The exploded-JAR layout inside the archive is:
WAR Deployment: When You Still Need It
Some organisations run a shared Tomcat or JBoss instance managed by operations. In that case you produce a WAR instead of a JAR. Change the packaging in pom.xml, extend SpringBootServletInitializer, and mark the embedded server as provided scope:
main() is still present, the same project runs as an executable JAR in development and can be deployed as a WAR in production. The provided scope on the Tomcat starter means it is excluded from the WAR (the container provides it) but kept on the classpath when running locally via main().
Summary
Spring Boot embeds Tomcat (or Jetty or Undertow) as a library inside your JAR, making java -jar the complete deployment unit. You configure the server through the server.* property namespace for common settings and through WebServerFactoryCustomizer for advanced tuning. Graceful shutdown with a configured timeout is a must for any production workload that expects zero-downtime restarts. When organisational constraints require an external container, switching to WAR packaging requires only three small changes and keeps the same source tree.