Jakarta EE is the current name of enterprise Java, and its Servlet specification is still the part almost every Java web app touches, even when a framework hides it. Spring MVC, Jakarta REST and JSF all sit on top of a servlet container. This page works as a compact Java servlet tutorial and JSP tutorial first, written against the jakarta packages, and then covers the javax to jakarta migration for code written before the rename.
Figures checked in November 2026.
What a servlet does
A servlet is a Java class that a container (Tomcat, Jetty, WildFly and others) calls for each matching HTTP request. The container parses the request, picks the servlet by URL pattern, calls a method such as doGet or doPost, and sends back whatever the servlet writes to the response. Threads, sockets, sessions and HTTP parsing are the container's job.
Since Servlet 3.0, a servlet does not need an entry in web.xml. An annotation is enough. Here is a complete one using the current packages:
package com.example;
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 req, HttpServletResponse resp)
throws IOException {
String name = req.getParameter("name");
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().println("Hello, " + (name == null ? "world" : name));
}
}
The API comes in as a provided dependency, because the container supplies it at runtime:
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
Package the project as a WAR (<packaging>war</packaging> in Maven), copy it into Tomcat's webapps folder, and the servlet answers at /<war-name>/hello. That copy-and-reload loop is the whole deployment model of the classic container.
A two-minute JSP tutorial
JavaServer Pages, now Jakarta Pages, turn an HTML file with embedded expressions into a servlet the first time it is requested. You write markup, and the container generates and compiles the Java class behind it. A small page that reads the same parameter looks like this:
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html>
<body>
<h1>Hello, <c:out value="${param.name}" default="world"/></h1>
</body>
</html>
The c:out tag escapes the value, which matters: printing a request parameter straight into HTML is the textbook cross-site scripting hole. To use the tag you need a Jakarta Tags (JSTL) implementation on the classpath, since Tomcat does not bundle one. Many JSP tutorials online still show <% ... %> scriptlets with Java code inside the page. They work, but they mix logic and markup in a way that makes pages hard to test, and the expression language with tags replaced them long ago.
Why javax became Jakarta EE
When Oracle handed Java EE to the Eclipse Foundation, the Java trademark stayed with Oracle, and the javax.* package names could not evolve under the new owner. The result was Jakarta EE 9, whose main change was mechanical: every enterprise API moved from javax.* to jakarta.*. Jakarta EE 8 still used javax and is functionally the same as Java EE 8. From EE 9 on, it is jakarta only.
The specification versions line up like this:
| Jakarta EE | Servlet version | Namespace |
|---|---|---|
| EE 8 | Servlet 4.0 | javax |
| EE 9 | Servlet 5.0 | jakarta |
| EE 10 | Servlet 6.0 | jakarta |
| EE 11 | Servlet 6.1 | jakarta |
| EE 12 (under development) | Servlet 6.2 | jakarta |
A class compiled against javax.servlet.http.HttpServlet is a different type from jakarta.servlet.http.HttpServlet. A jakarta container cannot load it at all, which is why an old WAR on a new Tomcat usually fails with a ClassNotFoundException or simply returns 404 for every servlet.
Tomcat vs Jetty: which server runs which namespace
The Tomcat version decides the namespace your code must use. These are the current branches from Apache's own "which version" table:
| Tomcat | Latest release | Servlet spec | Namespace | Minimum Java | Status |
|---|---|---|---|---|---|
| 11.0.x | 11.0.27 | 6.1 (EE 11) | jakarta | 17 | Supported |
| 10.1.x | 10.1.61 | 6.0 (EE 10) | jakarta | 11 | Supported |
| 9.0.x | 9.0.123 | 4.0 (EE 8) | javax | 8 | Supported until 31 March 2027; 9.1.x until 31 December 2030 |
| 10.0.x | 10.0.27 | 5.0 (EE 9) | jakarta | - | End of life since 2022-10-31 |
| 8.5.x | 8.5.100 | 3.1 | javax | - | End of life since 2024-03-31 |
Tomcat 9 is the last line that runs javax code. Apache announced in February 2026 that support for 9.0.x ends on 31 March 2027 and that a 9.1.x branch, with minimal differences, will receive releases until 31 December 2030. That moves the deadline for the javax to jakarta migration, but does not remove it. Frameworks force the same move: Spring Boot 4 is based on Jakarta EE 11 and requires a Servlet 6.1 container. Jetty is the one container that serves both worlds in a single release, since Jetty 12.1 supports Servlet 3.1 up to 6.1 in both namespaces. The Tomcat vs Jetty comparison covers that difference in detail.
Migration tools
Apache Tomcat Migration Tool for Jakarta EE
Apache ships a converter for compiled code. Version 1.0.13 is current. It takes an archive, a folder or a single file and writes a converted copy:
java -jar jakartaee-migration-1.0.13-shaded.jar myapp.war myapp-jakarta.war
It rewrites javax references in classes, JSPs and descriptors, which makes it the right tool when you only have the WAR or when a third-party library has no jakarta release. It strips signatures from signed JARs inside the archive and warns about each one. Tomcat can also run the tool for you: WARs placed in the directory named by the Host element's legacyAppBase attribute are converted with default settings and then deployed from the normal appBase.
OpenRewrite recipes
For source code you own, OpenRewrite changes imports, dependencies and configuration in the project itself, so the result is real jakarta code rather than a converted binary. The recipe for the namespace change is org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta ("Migrate to Jakarta EE 9"), part of the rewrite-migrate-java module. You can run it on a Maven project without editing the pom.xml:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-migrate-java:RELEASE \
-Drewrite.activeRecipes=org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta
The same module has org.openrewrite.java.migrate.jakarta.JakartaEE10 and JakartaEE11 recipes that go further and update platform dependencies. Run them on a clean branch and review the diff. Java SE packages such as javax.sql and javax.crypto stay untouched, which is correct, and it is exactly the case a hand-written search-and-replace gets wrong.
Once the code compiles against jakarta, test it on the container you will actually use in production. If that is a cloud host rather than your own Tomcat, the guide to deploying a Java app shows how to package it as a container image, and the Java versions page tells you which JDK release to run underneath.
The free servlet hosting years
Around 1999 to 2005, servlets were new enough that running one publicly was its own small achievement. Commercial hosts mostly offered Perl CGI and later PHP, and a handful of free services filled the gap with shared servlet containers. A member got a personal directory under a path like /~username/, uploaded class files and JSPs, and reached them through a /servlet/ URL on the provider's domain. Many early tutorials, mailing list answers and FAQ pages linked to accounts like these as live examples. Read any Java servlet tutorial from that time and the code still looks familiar. The API those pages taught, HttpServlet with doGet and doPost, is almost unchanged today. Only the package name in the first lines of the file is different.
Sources
- jakarta.ee/specifications/servlet
- jakarta.ee/specifications/platform/12
- tomcat.apache.org/whichversion.html
- tomcat.apache.org
- tomcat.apache.org/tomcat-9.0.x-eos.html
- tomcat.apache.org/download-migration.cgi
- github.com/apache/tomcat-jakartaee-migration
- tomcat.apache.org/tomcat-11.0-doc/config/host.html
- github.com/openrewrite/rewrite-migrate-java/blob/main/src/main/resources/META-INF/rewrite/jakarta-ee-9.yml
- docs.openrewrite.org/running-recipes/running-rewrite-on-a-maven-project-without-modifying-the-build
- github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide
- jetty.org/download.html
- wildfly.org/news/2026/10/22/WildFly-42-is-released