A silver bridge joining an early-2000s beige server to a modern violet compute module

Home / Guides

Servlet API guide

Jakarta EE Servlets and JSP: A Tutorial and javax to jakarta Migration

A servlet is still the layer under most Java web frameworks. Here is how one looks today, why every import changed from javax to jakarta, and the tools that move old code across.

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

  1. jakarta.ee/specifications/servlet
  2. jakarta.ee/specifications/platform/12
  3. tomcat.apache.org/whichversion.html
  4. tomcat.apache.org
  5. tomcat.apache.org/tomcat-9.0.x-eos.html
  6. tomcat.apache.org/download-migration.cgi
  7. github.com/apache/tomcat-jakartaee-migration
  8. tomcat.apache.org/tomcat-11.0-doc/config/host.html
  9. github.com/openrewrite/rewrite-migrate-java/blob/main/src/main/resources/META-INF/rewrite/jakarta-ee-9.yml
  10. docs.openrewrite.org/running-recipes/running-rewrite-on-a-maven-project-without-modifying-the-build
  11. github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide
  12. jetty.org/download.html
  13. wildfly.org/news/2026/10/22/WildFly-42-is-released

Guides / FAQ

Good questions.
Clear answers.

Can Tomcat 10 or 11 run an old javax servlet app?

Not directly. Tomcat 10.1 and 11 only load classes that use the jakarta.servlet packages. You can convert the WAR with the Apache Tomcat Migration Tool for Jakarta EE, or place it in the directory set by the Host's legacyAppBase attribute, where Tomcat converts it with the same tool before deploying. Tomcat 9 still runs javax apps until its support ends on 31 March 2027.

Do all javax packages change to jakarta?

No. Only the enterprise specifications moved, such as javax.servlet, javax.persistence and javax.ws.rs. Packages that belong to Java SE itself, such as javax.sql, javax.crypto and javax.net, keep their names. A blind search-and-replace of every javax import breaks those, which is why the dedicated migration tools are safer.

Is JSP still used with Jakarta EE?

Yes, under the name Jakarta Pages. Tomcat 11 implements Pages 4.0 together with Servlet 6.1. New projects more often render HTML with a template engine or a JavaScript front end, but JSP remains common in existing applications and works fine for server-rendered pages.

Which Jakarta EE version should a new servlet project target?

Target Jakarta EE 11, which means Servlet 6.1. Tomcat 11, Jetty 12.1, WildFly 42, Payara 7, Open Liberty and GlassFish 8 all support it, and Spring Boot 4 requires a Servlet 6.1 baseline. Use Servlet 6.0 only if you must deploy on Tomcat 10.1.

What does the Tomcat migration tool change in a WAR?

It rewrites javax references to jakarta in classes, JSPs and descriptors inside the archive, a folder or a single file, and writes the result to a new location. It also removes cryptographic signatures from JAR files inside the source and logs a warning for each one, because a modified JAR no longer matches its signature.

Find your next step.