org.apache.tomcat.embed:tomcat-embed-core Explained: What It Is and How to Use It
Learn what org.apache.tomcat.embed:tomcat-embed-core is, why Spring Boot includes it by default, how to manage its version, and what to do when dependency conflicts arise.
If you’ve opened a Spring Boot project’s dependency tree and spotted org.apache.tomcat.embed:tomcat-embed-core, you’re looking at the embedded Tomcat server that runs your application without needing a separate server installation. This artifact is what makes java -jar myapp.jar work as a standalone web server. This post explains what it does, why it’s there, how versioning works, and how to handle the most common issues developers run into with it.
What Is org.apache.tomcat.embed:tomcat-embed-core?
org.apache.tomcat.embed:tomcat-embed-core is the core module of Apache Tomcat packaged as an embeddable library. Instead of deploying your application to a standalone Tomcat server installed on a machine, this dependency bundles the server runtime directly inside your JAR file.
The full Maven coordinates look like this:
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>10.1.20</version>
</dependency>
You almost never add this dependency directly. Spring Boot pulls it in as a transitive dependency when you include spring-boot-starter-web:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
That starter brings in spring-boot-starter-tomcat, which depends on three Tomcat embed modules:
tomcat-embed-core: The HTTP server and servlet container implementationtomcat-embed-el: Expression Language supporttomcat-embed-websocket: WebSocket support
tomcat-embed-core is the foundation. The other two sit on top of it.
Why Spring Boot Uses Embedded Tomcat by Default
Traditional Java web applications were packaged as WAR files and deployed to an external application server. That model works but adds operational complexity: you need to install, configure, and manage a server separately from your application.
Spring Boot took a different approach. By embedding the server inside the application JAR, the application becomes self-contained. You run it with java -jar and it starts its own HTTP server, listens on a port, and handles requests. No separate server setup required.
Embedded Tomcat made this practical because Apache Tomcat already supported an embedded mode through its embed modules. Spring Boot’s autoconfiguration wires everything together so you get a working HTTP server with sensible defaults and zero manual configuration.
The result is that org.apache.tomcat.embed:tomcat-embed-core ends up in virtually every Spring Boot web application’s dependency tree, whether the developer explicitly chose it or not.
How Versioning Works
Spring Boot manages the version of tomcat-embed-core through its parent POM or BOM (Bill of Materials). When you inherit from spring-boot-starter-parent or import the Spring Boot BOM, you get a curated set of dependency versions that are tested to work together:
<!-- Parent approach -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<!-- BOM approach -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Each Spring Boot version maps to a specific Tomcat version. Spring Boot 3.x uses Tomcat 10.x. Spring Boot 2.x used Tomcat 9.x. The jump from Tomcat 9 to 10 was significant because it moved from the javax.servlet namespace to jakarta.servlet, following the Jakarta EE migration.
You can check which Tomcat version your Spring Boot version pulls in by looking at the Spring Boot release notes or running:
mvn dependency:tree | grep tomcat-embed-core
Or with Gradle:
./gradlew dependencies | grep tomcat-embed-core
Overriding the Tomcat Version
Sometimes you need a specific Tomcat version, either to pick up a security patch or to match a dependency requirement. In a Maven project using spring-boot-starter-parent, override the version through a property:
<properties>
<tomcat.version>10.1.24</tomcat.version>
</properties>
Spring Boot’s parent POM exposes tomcat.version as the property that controls all three tomcat-embed-* artifacts. Setting it in one place updates all of them consistently.
In a Gradle project:
ext['tomcat.version'] = '10.1.24'
Or in Kotlin DSL:
extra["tomcat.version"] = "10.1.24"
Avoid overriding the version in individual dependency declarations because you’ll end up with mismatched versions between tomcat-embed-core, tomcat-embed-el, and tomcat-embed-websocket, which can cause ClassNotFoundException or NoSuchMethodError at runtime.
Switching From Tomcat to Jetty or Undertow
org.apache.tomcat.embed:tomcat-embed-core is the default, but Spring Boot supports other embedded servers. If you want to use Jetty or Undertow instead, exclude the Tomcat starter and add the alternative:
Switching to Jetty (Maven):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
Switching to Undertow (Maven):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
Reasons to switch include performance characteristics, memory footprint, or compatibility requirements. Tomcat is the best-tested and most documented option for Spring Boot, so only switch if you have a specific reason.
Deploying to an External Server: Removing the Embedded Tomcat
If you’re deploying to an external Tomcat or another servlet container (as a WAR file), you need to mark the embedded Tomcat dependency as provided so it’s available at compile time but not included in the WAR:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
You also need to change your packaging to war and extend SpringBootServletInitializer. This is the traditional deployment model, and it means tomcat-embed-core ends up on the classpath from the external server rather than bundled in your artifact.
Most teams building new applications stick with the embedded model. It’s simpler to deploy, easier to version, and works well with containers and cloud platforms. The WAR deployment approach makes more sense when you’re working with existing infrastructure that requires it.
Common Issues and How to Fix Them
Version Conflict Errors
If another dependency in your project pulls in a different version of tomcat-embed-core, you can get classpath conflicts. The symptom is usually a NoSuchMethodError or ClassNotFoundException at startup mentioning a Tomcat class.
Run mvn dependency:tree to see what’s pulling in the conflicting version, then use an exclusion to remove the conflicting transitive dependency and let Spring Boot’s managed version win.
Port Already in Use
Web server failed to start. Port 8080 was already in use.
This isn’t a tomcat-embed-core bug, but it’s the most common startup failure. Change the port in application.properties:
server.port=8081
Or find and stop the process using port 8080.
javax vs jakarta Namespace Errors
If you’re migrating from Spring Boot 2.x to 3.x, code that imports javax.servlet.* will break because Tomcat 10 moved everything to jakarta.servlet.*. Search for javax.servlet imports in your codebase and update them to jakarta.servlet.
Configuring Embedded Tomcat Programmatically
Spring Boot lets you customize the embedded Tomcat instance through a WebServerFactoryCustomizer:
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
return factory -> {
factory.setPort(8443);
factory.addConnectorCustomizers(connector -> {
connector.setMaxPostSize(10 * 1024 * 1024); // 10MB
});
};
}
This gives you access to Tomcat-specific settings that don’t have Spring Boot properties equivalents, like connector thread pool sizes, max connections, and compression settings.
Security Considerations
org.apache.tomcat.embed:tomcat-embed-core has had security vulnerabilities over the years, as any active project does. Keeping it updated is important. Since Spring Boot manages the version, updating your Spring Boot version is usually the right path.
Tools like OWASP Dependency Check, Snyk, or GitHub’s Dependabot can flag known CVEs in your dependency tree, including in tomcat-embed-core. Running dependency scans as part of your CI pipeline is a solid practice for any production application.
Staying on top of dependency security connects to broader software quality practices that protect your application and its users. Teams serious about application reliability also benefit from understanding how performance testing interacts with server configuration, since embedded Tomcat settings directly affect throughput and response times under load. And if you’re working in environments where DevOps tooling manages your deployment pipeline, knowing which server artifact your app uses is relevant for container sizing and health check configuration.
Key Takeaways
org.apache.tomcat.embed:tomcat-embed-coreis the embedded Apache Tomcat library that Spring Boot uses to run your application as a standalone JAR- You almost never add it directly; it comes in as a transitive dependency through
spring-boot-starter-web - Spring Boot manages its version through the parent POM or BOM; override it with the
tomcat.versionproperty when needed - Spring Boot 3.x uses Tomcat 10.x with the
jakarta.servletnamespace; Spring Boot 2.x used Tomcat 9.x withjavax.servlet - Switch to Jetty or Undertow by excluding
spring-boot-starter-tomcatand adding the alternative starter - For WAR deployment to an external server, scope
spring-boot-starter-tomcatasprovided - Keep the version current through Spring Boot updates and scan for CVEs as part of your build process
Once you understand that org.apache.tomcat.embed:tomcat-embed-core is just Tomcat packaged for embedding, most questions about it resolve themselves. It behaves like Tomcat, configures like Tomcat, and responds to the same tuning parameters, just wired in through Spring Boot’s abstraction layer rather than XML configuration files.