Proper Jakarta EE Library Setup in Maven for Tomcat
With the transition from Java EE to Jakarta EE, many enterprise Java developers face runtime issues when deploying applications on Apache Tomcat. These problems usually arise due to incorrect Maven dependency scopes, mismatched Jakarta versions, or misunderstanding what Tomcat actually provides at runtime. Let us delve into understanding how to configure Java Jakarta libraries for Tomcat effectively.
1. Understanding Jakarta EE Capabilities in Tomcat
Apache Tomcat is not a full Jakarta EE application server. It is a Servlet container. This distinction is critical when configuring dependencies.
1.1 What Tomcat Provides?
Depending on the Tomcat version in use, it provides a limited but essential set of Jakarta EE specifications out of the box. These built-in APIs focus mainly on web and HTTP-related functionalities, allowing developers to build and deploy Jakarta-based web applications without adding extra dependencies for these specifications.
The following Jakarta specifications are included by default with Apache Tomcat:
- Jakarta Servlet – Provides the core API for handling HTTP requests and responses. It is the foundation for building web applications, REST endpoints, filters, and listeners in a Jakarta-based application.
- Jakarta JSP – Enables server-side rendering of dynamic web pages using JavaServer Pages. Tomcat includes a JSP engine that compiles JSP files into servlets at runtime.
- Jakarta Expression Language (EL) – Used within JSP and other view technologies to simplify access to application data, beans, and scoped variables using concise expressions.
- Jakarta WebSocket – Supports full-duplex, real-time communication between client and server, making it suitable for chat applications, live notifications, and streaming use cases.
It is important to note that Tomcat is not a full Jakarta EE application server. Specifications such as Jakarta Persistence (JPA), Jakarta Transactions (JTA), Jakarta Messaging (JMS), and Jakarta RESTful Web Services (JAX-RS) are not included by default and must be explicitly added to the Maven pom.xml when required.
1.2 Tomcat Version Mapping
The table below highlights how different Apache Tomcat versions align with specific Servlet API versions and namespace changes. This mapping is crucial when configuring Jakarta EE dependencies in your Maven pom.xml, as a mismatch between the Tomcat runtime and the API namespace can lead to runtime errors such as ClassNotFoundException or NoClassDefFoundError.
| Tomcat Version | Servlet API | Jakarta Namespace |
|---|---|---|
| Tomcat 9 | Servlet 4.0 | javax.* |
| Tomcat 10.0 | Servlet 5.0 | jakarta.* |
| Tomcat 10.1+ | Servlet 6.0 | jakarta.* |
1.3 Frequent Configuration Errors
While configuring Jakarta EE libraries for Apache Tomcat, developers often encounter issues that lead to build failures, deployment errors, or unexpected runtime behavior. The following are some of the most common pitfalls and why they should be avoided.
1.3.1 Packaging Servlet APIs in the WAR
Tomcat already ships with the Servlet API that matches its supported specification. Adding the Servlet API directly to your application’s WAR file leads to duplicate classes being loaded by different classloaders.
<dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>stable__jar__version</version> </dependency>
This causes classloader conflicts because Tomcat already provides this API. Common symptoms include LinkageError, unexpected behavior at runtime, or deployment warnings during server startup. The correct approach is to mark this dependency with scope as provided, ensuring it is available at compile time but not packaged in the final artifact.
1.3.2 Mixing Java EE and Jakarta EE Namespaces
Starting with Jakarta EE 9, all APIs moved from the javax.* namespace to jakarta.*. Using legacy Java EE imports in an application deployed to Tomcat 10 or later will result in runtime failures.
import javax.servlet.http.HttpServlet;
This will fail on Tomcat 10+ with a ClassNotFoundException because the server only exposes jakarta.servlet.* classes. Ensure that all imports, dependencies, and configuration files (such as web.xml) consistently use the jakarta.* namespace.
1.3.3 Assuming Tomcat Provides All Jakarta EE APIs
Apache Tomcat is a lightweight Servlet container, not a full Jakarta EE application server. It only includes a subset of Jakarta specifications focused on web functionality.
Tomcat does not provide the following Jakarta EE APIs:
- Jakarta Persistence (JPA)
- Jakarta Contexts and Dependency Injection (CDI)
- Jakarta RESTful Web Services (JAX-RS)
- Enterprise JavaBeans (EJB)
Assuming these APIs are available by default will lead to ClassNotFoundException or missing provider errors at runtime. These specifications must be explicitly added as Maven dependencies, along with compatible implementations (for example, Hibernate for JPA or Jersey for JAX-RS), and configured correctly within the application.
2. Recommended Configuration Approach
2.1 Add Maven Dependencies (pom.xml)
<dependency> <groupId>jakarta.servlet.jsp</groupId> <artifactId>jakarta.servlet.jsp-api</artifactId> <version>stable__jar__version</version> <scope>provided</scope> </dependency> <dependency> <groupId>jakarta.servlet.jsp.jstl</groupId> <artifactId>jakarta.servlet.jsp.jstl-api</artifactId> <version>stable__jar__version</version> </dependency> <dependency> <groupId>org.glassfish.web</groupId> <artifactId>jakarta.servlet.jsp.jstl</artifactId> <version>stable__jar__version</version> </dependency> <dependency> <groupId>jakarta.persistence</groupId> <artifactId>jakarta.persistence-api</artifactId> <version>stable__jar__version</version> </dependency> <dependency> <groupId>org.hibernate.orm</groupId> <artifactId>hibernate-core</artifactId> <version>stable__jar__version</version> </dependency>
The above Maven dependencies configure JSP, JSTL, and JPA support for a Jakarta EE application running on Apache Tomcat. Each dependency serves a specific purpose and must be scoped correctly to avoid conflicts with the container.
- The
jakarta.servlet.jsp-apidependency provides the core Jakarta Server Pages (JSP) interfaces required at compile time. Since Tomcat already includes the JSP implementation at runtime, this dependency is marked withscopeasprovided. This ensures the API is available during compilation but is not packaged into the WAR file, preventing classloader conflicts. - The
jakarta.servlet.jsp.jstl-apidependency contains only the JSTL interfaces and tag library definitions. Tomcat does not bundle JSTL by default, so this API must be explicitly included in the application to allow JSP pages to compile and recognize JSTL tags. - The
org.glassfish.web:jakarta.servlet.jsp.jstldependency is the reference implementation of JSTL. While the API defines the contracts, this implementation provides the actual runtime behavior for JSTL tags such as<c:forEach>,<c:if>, and formatting tags. Both the API and implementation are required for JSTL to function correctly. - The
jakarta.persistence-apidependency defines the standard Jakarta Persistence interfaces, annotations, and entity mappings. Tomcat does not include JPA support, so this API must be added explicitly when building applications that interact with relational databases using ORM techniques. - The
hibernate-coredependency is a popular JPA implementation. It provides the actual persistence engine that processes JPA annotations, manages entity lifecycle, and executes SQL queries against the database. This dependency works together with the JPA API to enable full persistence functionality in a Tomcat-based Jakarta application.
2.2 Servlet Code
// HelloServlet.java
package com.example.web;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException,
IOException {
response.setContentType("text/plain");
response.getWriter().println("Hello Jakarta EE on Tomcat!");
}
}
This servlet example demonstrates a simple Jakarta EE–based web component running on Apache Tomcat using the jakarta.servlet namespace. The HelloServlet class extends HttpServlet and is annotated with @WebServlet("/hello"), which eliminates the need for a web.xml entry by declaratively mapping the servlet to the /hello URL. The doGet method is overridden to handle HTTP GET requests, where the response content type is set to plain text and a message is written directly to the HTTP response body using the servlet output writer. When this application is deployed on Tomcat 10 or later and the endpoint /hello is accessed in a browser or via a REST client, the server responds with the text “Hello Jakarta EE on Tomcat!”, confirming that the Jakarta namespace is correctly configured and the servlet is successfully executed.
2.3 Testing and Verifying the Configuration
After configuring the Jakarta EE dependencies correctly, it is important to verify that the application builds, packages, and runs as expected on Apache Tomcat. The following steps help ensure that there are no classpath or namespace issues.
2.3.1 Build the Application
Use Maven to clean and package the application. This step compiles the source code, resolves dependencies, and generates the final WAR file.
mvn clean package
A successful build without errors indicates that all Jakarta imports and dependencies are correctly resolved at compile time.
2.3.2 Inspect the WAR
Before deploying, inspect the generated WAR file (for example, by unzipping it) and verify that the following libraries are not present under WEB-INF/lib, as Tomcat already provides them:
jakarta.servlet-apijakarta.servlet.jsp-api
Their absence confirms that these dependencies were correctly marked with scope as provided and will be supplied by the Tomcat runtime.
2.3.3 Deploy to Tomcat 10+
Copy the generated WAR file into the $TOMCAT_HOME/webapps directory and start or restart Tomcat. Once the server is running, access the application using the following URL: http://localhost:8080/your-app/hello. If the configuration is correct, the servlet will be loaded successfully and the following output will be displayed in the browser:
Hello Jakarta EE on Tomcat!
This output confirms that the Jakarta EE APIs are correctly configured, the application is deployed without classloading conflicts, and Tomcat is successfully executing the Jakarta-based servlet.
3. Conclusion
Correctly configuring Jakarta EE libraries in Maven for Tomcat comes down to understanding what Tomcat provides versus what your application must include: use scope=provided for servlet-related APIs to avoid classloader conflicts, never mix javax.* and jakarta.* namespaces within the same application, explicitly add any missing Jakarta specifications that Tomcat does not supply by default, and always match the Tomcat version with the appropriate Jakarta namespace; following these practices ensures a clean, portable, and production-ready Jakarta EE application on Apache Tomcat.




