Jakarta Servlet
Jakarta Servlet is a server-side API specification within the Jakarta EE platform that enables Java developers to create dynamic web content by handling HTTP requests and responses in a standardized manner.[1] Developed as the successor to the Java Servlet technology, it provides a contract between servlet containers and applications, allowing for the extension of web server functionality without modifying the server itself.[2]
Originally introduced as Java Servlet in 1996 by Sun Microsystems, the technology formed the foundation of Java-based web development and was integrated into Java EE (formerly J2EE).[3] In 2017, Oracle transferred stewardship of Java EE to the Eclipse Foundation, leading to its rebranding as Jakarta EE in 2019 to reflect the open-source governance and avoid trademark issues with "Java."[4][5] This transition included renaming the package namespace from javax.servlet to jakarta.servlet starting with Jakarta EE 9 in 2020, ensuring compatibility with modern enterprise Java ecosystems.[4]
Key features of Jakarta Servlet include its lifecycle management—encompassing initialization ([init](/page/Init)), request processing ([service](/page/Service)), and cleanup (destroy) methods—handled by the servlet container, such as Apache Tomcat.[2] It supports asynchronous processing, non-blocking I/O for improved performance, multipart file uploads, and HTTP/2 features like server push, making it suitable for both presentation-oriented and service-oriented web applications.[2] The HttpServlet class, an abstract extension of the core Servlet interface, provides methods like doGet and doPost to handle specific HTTP methods.[2]
As of 2025, the latest stable version is Jakarta Servlet 6.1, released in April 2024 as part of Jakarta EE 11, with version 6.2 under development for Jakarta EE 12.[1] This evolution continues to emphasize portability, security enhancements, and integration with other Jakarta EE specifications like Jakarta Server Pages (JSP) and Jakarta Faces for building scalable web applications.[1]
Overview
Definition and Purpose
A Jakarta servlet is a Java class that implements the jakarta.servlet.Servlet interface, serving as a server-side component designed to process client requests and generate dynamic responses within a web application environment. It operates under the management of a servlet container, which handles the deployment, execution, and lifecycle of servlets on a web server or application server. This architecture allows servlets to extend the functionality of web servers by responding to HTTP requests in a structured, programmatic manner.[6][7]
The primary purpose of Jakarta Servlet is to enable the development of dynamic web applications that go beyond static content delivery, facilitating tasks such as processing HTML form submissions, querying databases, generating customized output, and maintaining user sessions across multiple interactions. By converting HTTP requests into Java objects and providing corresponding response mechanisms, servlets reduce the complexity of handling web protocols directly, allowing developers to focus on application logic. This capability supports both presentation-oriented web pages and service-oriented architectures, such as RESTful APIs, making it a foundational technology for scalable web services.[2][8]
Leveraging Java's platform independence, Jakarta Servlet ensures portability across different operating systems and hardware configurations that support the Java Virtual Machine (JVM), without requiring recompilation or vendor-specific adjustments. As an integral part of the Jakarta EE platform, it integrates with other specifications like Jakarta Server Pages (JSP) and Jakarta Enterprise Beans (EJB), enabling the construction of robust, enterprise-grade applications that adhere to standardized APIs for interoperability and maintainability.[7][9]
Key Features
Jakarta Servlets provide built-in support for multithreading, where the servlet container creates a dedicated thread for each incoming request, enabling efficient handling of concurrent client interactions without blocking other operations. This architecture allows web applications to achieve high concurrency and responsiveness, though it necessitates careful management of shared resources to prevent race conditions, often through synchronization mechanisms.[10]
A significant advancement since Servlet 3.0 is the introduction of annotations for declarative configuration, exemplified by the @WebServlet annotation, which maps servlet classes to URL patterns directly in the code, eliminating the need for verbose XML deployment descriptors. This feature streamlines development by promoting a more concise and annotation-driven approach to servlet registration and initialization.
Extensibility is a core strength of Jakarta Servlets, achieved through standardized interfaces that facilitate customization for various aspects of web processing. The HttpServlet class extends basic servlet functionality to handle HTTP-specific methods like GET and POST, while the HttpSession interface enables robust session management for maintaining state across requests. Additionally, error management is supported via interfaces and mechanisms for exception propagation and configurable error responses, allowing developers to implement tailored handling for faults and exceptions.[2]
Asynchronous processing, first introduced in Servlet 3.0 and further refined with non-blocking I/O support in Servlet 3.1, permits servlets to delegate long-running tasks to background threads, freeing the primary request thread for other work and enhancing performance in scenarios involving database queries or external service calls. This capability is particularly valuable for scalable applications dealing with high-latency operations.[2]
History and Evolution
Origins in Java Servlet Technology
Servlet technology originated at Sun Microsystems in the mid-1990s, emerging as a server-side extension to the Java platform for generating dynamic web content. In 1997, Sun released the beta version of the Java Web Server, which included the initial implementation of servlets as a core feature, allowing developers to create portable server-side applications in Java. The first formal specification was Java Servlet API 1.0, released in December 1996.[11][12]
The development of servlets was primarily motivated by the shortcomings of the Common Gateway Interface (CGI), the prevailing method for dynamic web scripting at the time. CGI required spawning a separate operating system process for each HTTP request, leading to significant performance overhead, especially under high load, and limited portability across server environments due to language-specific dependencies. In contrast, servlets executed within the web server's JVM using lightweight threads, enabling persistent execution, reduced startup costs, and seamless scalability while leveraging Java's cross-platform compatibility for broader deployment.[13]
Early adoptions of servlet technology quickly followed its introduction, with developers integrating it into the Java Web Server and early application servers to replace CGI-based scripts in production web applications. This shift was driven by demonstrated performance gains—servlets could handle thousands of requests per second on modest hardware compared to CGI's hundreds—and the ability to maintain stateful sessions and share resources efficiently across requests. By standardizing server-side Java development, servlets facilitated the growth of enterprise web applications, laying the groundwork for more robust architectures. Version 2.0 of the API was released in December 1997, followed by version 2.2 in August 1999 as part of J2EE 1.2, establishing a standardized API for servlet lifecycle management, request/response handling, and configuration under the Java Enterprise Edition platform, which accelerated widespread industry adoption.[14][12]
Transition to Jakarta EE
In September 2017, Oracle announced its decision to donate the Java EE platform, including its specifications and related technologies, to the Eclipse Foundation to foster open-source innovation and community-driven development. This transfer, supported by key industry players like IBM and Red Hat, aimed to accelerate the evolution of enterprise Java under a vendor-neutral governance model. The donation process completed in early 2018, paving the way for the rebranding and initial releases under the Eclipse Foundation.
The first major milestone following the donation was the release of Jakarta EE 8 on September 10, 2019, which served as a direct compatibility bridge from Java EE 8, incorporating all its specifications without functional changes but establishing the new branding and open governance structure. For Jakarta Servlet specifically, this release maintained the existing javax.servlet package namespace and API compatibility, allowing seamless adoption by existing Java EE 8 applications and servers. The transition emphasized community collaboration, with contributions from multiple vendors ensuring the platform's stability and forward momentum.[15][16]
A pivotal change arrived with Jakarta EE 9, released on December 8, 2020, which mandated a full namespace migration from javax.* to jakarta.* packages to resolve trademark restrictions on the "Java" name held by Oracle. In the context of Jakarta Servlet, this required updating imports and references from javax.servlet.* to jakarta.servlet.*, along with adjustments to deployment descriptors like web.xml schema versions and potential recompilation of bytecode. The shift, while breaking binary compatibility with prior versions, was designed to enable independent evolution of the platform without legal encumbrances.[17][18]
Servlet developers faced challenges in migrating legacy applications, including updating dependencies in build tools like Maven or Gradle to use jakarta.servlet-api instead of javax.servlet-api, and testing for runtime compatibility in updated containers such as Tomcat 10 or later. To facilitate this, the Eclipse Foundation developed the Eclipse Transformer tool, which automates XML and JAR transformations for namespace changes, while Apache provides a dedicated migration tool for Tomcat-based deployments that handles servlet-specific conversions at build or runtime. These efforts, combined with compatibility matrices from vendors like Payara and WildFly, have supported widespread adoption, with ongoing ecosystem updates ensuring backward-compatible paths for gradual transitions.[19]
Specification Details
Versions and Standards
The Jakarta Servlet specification originated as Java Servlet technology under Sun Microsystems and evolved through the Java Community Process (JCP) before transitioning to the Eclipse Foundation. The initial Java Servlet 1.0 specification was released in December 1996 as part of the Java Web Server development kit, providing the foundational API for server-side HTTP request handling.[20] Subsequent versions built incrementally, with major advancements in areas such as configuration, asynchronous processing, and protocol support, while maintaining strong backward compatibility to ensure existing applications could migrate with minimal changes.
Key releases include Java Servlet 2.3 (August 2001, JSR 53), which introduced support for web application deployment descriptors and error handling; Java Servlet 3.0 (December 2009, JSR 315), which added annotation-based configuration to simplify development and reduce XML reliance; Java Servlet 3.1 (May 2013, JSR 340), enhancing asynchronous I/O for better scalability; and Java Servlet 4.0 (September 2017, JSR 369), incorporating HTTP/2 protocol support including server push and improved upgrade mechanisms.[21][22][23][24] Each major version deprecated select features, such as certain listener interfaces in 3.0, but provided migration paths and compatibility guarantees for prior APIs.[22]
Following Oracle's donation of Java EE to the Eclipse Foundation in 2017, the specification was rebranded as Jakarta Servlet starting with version 5.0 (October 2020, for Jakarta EE 9), which primarily renamed the javax.servlet namespace to jakarta.servlet to avoid trademark issues, with no functional changes but full backward compatibility for recompiled applications.[25] Jakarta Servlet 6.0 (June 2022, for Jakarta EE 10) introduced support for Java SE 11, enhanced HTTP status code handling, and deprecated the SingleThreadModel interface, emphasizing modern concurrency models while preserving compatibility with 5.0.[7] The latest stable release, Jakarta Servlet 6.1 (April 2024, for Jakarta EE 11), removed SecurityManager references in light of its deprecation in Java SE 18 and added minor clarifications for async context management, ensuring seamless upgrades from 6.0 with no breaking changes.[26] Version 6.2 is currently under development for Jakarta EE 12.[1]
The standardization process shifted from the JCP's Java Specification Requests (JSRs), managed by Sun and later Oracle, to the Eclipse Foundation's Specification Process post-2017, involving working groups for collaborative development and compatibility testing.[20] Across all versions, the specification has prioritized backward compatibility, with deprecations clearly documented to allow gradual adoption, enabling servlets from as early as version 1.0 to run in modern containers after recompilation where namespace changes apply.[10]
Core Components and APIs
The Jakarta Servlet API is structured around two primary packages: jakarta.servlet, which provides protocol-independent foundational elements, and jakarta.servlet.http, which extends these for HTTP-specific operations. These packages define the contracts between servlets and the runtime environment, ensuring portability across compliant servlet containers.
In the jakarta.servlet package, the Servlet interface forms the core contract for all servlets, mandating the implementation of methods such as init(ServletConfig config), service(ServletRequest req, ServletResponse res), and destroy() to handle initialization, request processing, and cleanup, respectively. The GenericServlet abstract class implements this interface, offering a basic, protocol-agnostic skeleton that simplifies development by handling common tasks like configuration access and logging. For initialization, the ServletConfig interface delivers deployment-specific parameters to the servlet during startup, allowing customization without code changes. Complementing this, the ServletContext interface provides a shared view of the web application, enabling servlets to retrieve global resources, MIME types, and attributes, or to dispatch requests to other components within the application scope. Additional supporting interfaces include ServletRequest, which encapsulates incoming client data like parameters and attributes, and ServletResponse, which facilitates outgoing data formatting and transmission, both abstracting protocol details for generality.
The jakarta.servlet.http package specializes these core elements for HTTP, the predominant protocol in web development. The HttpServlet abstract class extends GenericServlet and overrides the service method to route HTTP requests to specialized handlers like doGet(HttpServletRequest req, HttpServletResponse resp) for retrieving resources and doPost(HttpServletRequest req, HttpServletResponse resp) for submitting data, streamlining HTTP request-response patterns. The HttpServletRequest interface builds on ServletRequest by adding HTTP-centric features, such as access to request headers, cookies, sessions via getSession(), and URI details, essential for stateful web interactions. Likewise, HttpServletResponse extends ServletResponse to support HTTP-specific outputs, including setting status codes with setStatus(int sc), adding headers via setHeader(String name, String value), and writing content streams or redirecting clients. Together, these APIs enable robust handling of web requests while maintaining separation of concerns between protocol logic and business functionality.
Servlet Life Cycle
Initialization and Loading
The servlet container plays a central role in the initialization and loading phase of a servlet's lifecycle, managing the process to ensure the servlet is ready to handle requests. Upon deployment or when a matching request arrives (lazy loading), the container first loads the servlet class using standard Java class loading mechanisms, which may involve loading from local files, remote sources, or network services. Once loaded, the container instantiates the servlet by invoking its public no-argument constructor, typically through reflection, creating a single instance per servlet declaration in non-distributed environments or one per JVM in distributed setups.[27][28]
After instantiation, the container calls the init(ServletConfig config) method exactly once to initialize the servlet, passing a ServletConfig object that provides access to the servlet's configuration and the broader ServletContext. This method allows the servlet to perform one-time setup tasks, such as allocating resources, establishing database connections, or reading configuration data, before it can service any requests. The init method must complete successfully for the servlet to enter service; the container ensures thread safety and visibility as per Java memory model requirements.[28][27]
Initialization parameters, which supply servlet-specific configuration data, are handled through the ServletConfig object and can be declared either in the web.xml deployment descriptor or via annotations. In web.xml, parameters are defined within the <servlet> element using <init-param> sub-elements, specifying a <param-name> and <param-value>, for example:
xml
<servlet>
<servlet-name>ExampleServlet</servlet-name>
<servlet-class>com.example.ExampleServlet</servlet-class>
<init-param>
<param-name>databaseUrl</param-name>
<param-value>jdbc:[mysql](/page/MySQL)://localhost:3306/mydb</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>ExampleServlet</servlet-name>
<servlet-class>com.example.ExampleServlet</servlet-class>
<init-param>
<param-name>databaseUrl</param-name>
<param-value>jdbc:[mysql](/page/MySQL)://localhost:3306/mydb</param-value>
</init-param>
</servlet>
Alternatively, since Servlet 3.0, annotations like @WebServlet with nested @WebInitParam enable programmatic configuration directly in the servlet class, such as @WebServlet(urlPatterns = {"/example"}, initParams = {@WebInitParam(name = "databaseUrl", value = "jdbc:mysql://[localhost](/page/Localhost):3306/mydb")}), replacing equivalent XML entries. These parameters are accessible via config.getInitParameter("name") during initialization.[2][29]
Error scenarios during initialization are managed through exceptions thrown by the init method. If initialization fails due to issues like resource unavailability or configuration errors, the servlet can throw a ServletException, which may wrap an UnavailableException to signal the servlet's unavailability. An UnavailableException indicates either a permanent failure (e.g., critical misconfiguration requiring administrative intervention) or a temporary one (e.g., transient system overload, with an optional duration estimate in seconds); in such cases, the container does not invoke destroy and may remove the servlet instance or defer requests accordingly.[30][28]
Request Handling and Service
In the active service phase of a Jakarta Servlet, the servlet container invokes the service(ServletRequest req, ServletResponse res) method for each incoming client request routed to the servlet instance. This method receives a request object encapsulating the client's input and a response object for generating the server's output, enabling the servlet to process the request and produce a corresponding response.[10]
For HTTP-based requests, the HttpServlet subclass implements the service method to automatically dispatch to specialized handler methods based on the HTTP method. For example, an HTTP GET request triggers doGet(HttpServletRequest req, HttpServletResponse resp), while a POST request invokes doPost(HttpServletRequest req, HttpServletResponse resp). This dispatching mechanism allows developers to implement request-specific logic in the overridden methods without directly managing the HTTP protocol details.[10][31]
The threading model employs one thread per request, with a single servlet instance shared across multiple threads to handle concurrent requests efficiently. This multi-threaded approach requires developers to implement thread-safe practices, such as synchronizing access to shared resources, as the container may route simultaneous requests through the service method without inherent synchronization. The deprecated SingleThreadModel interface, once used to enforce single-threaded execution, is no longer recommended due to performance overhead and has been removed in Servlet 6.0.[10]
Session management occurs through the HttpSession interface, which maintains state across multiple requests from the same client by associating data with a unique session identifier. Servlets access the session via HttpServletRequest.getSession(), binding attributes with setAttribute(String name, Object value) for persistence until session expiration or invalidation. By default, sessions are tracked using HTTP cookies named JSESSIONID, which the container sets in the response and retrieves from subsequent requests; support for cookie-based tracking is mandatory in all servlet containers.[10]
Destruction and Cleanup
The destruction phase marks the final stage in the servlet lifecycle, where the container notifies the servlet instance that it is being taken out of service. This occurs after the servlet has completed processing all incoming requests, allowing it to perform necessary cleanup operations before the instance is garbage collected.[10]
The container invokes the destroy() method on the servlet once it determines the servlet should be removed from service, such as to conserve system resources or during application shutdown. This method is called only after all threads executing the servlet's service() method have exited or after a server-defined timeout period has elapsed, ensuring no ongoing request handling interferes with cleanup. Its primary purpose is to enable the servlet to release held resources, such as database connections, file handles, or thread pools, and to save any persistent state to synchronize with in-memory data. For example, a servlet managing a connection pool would close all open connections in this method to prevent resource exhaustion.[10][32]
The timing of the destroy() call is triggered by events like a configurable idle period elapsing without new requests, explicit application undeployment, or container shutdown; it is not invoked if the servlet's initialization failed. To implement destroy() effectively and avoid memory leaks, developers should systematically release all acquired resources, such as closing database connections or stopping background threads, while using flags to signal long-running operations to terminate gracefully. Best practices include tracking active service invocations with a counter to confirm completion and notifying dependent components to shut down, ensuring the servlet does not retain references that could hinder garbage collection.[10][2]
Development and Deployment
Creating a Basic Servlet
To create a basic servlet in Jakarta Servlet, developers typically extend the HttpServlet class from the jakarta.servlet.http package, which provides the foundational API for handling HTTP requests and responses.[33] This approach allows overriding specific methods like doGet() for GET requests or doPost() for POST requests to implement custom logic, such as generating dynamic content.[33]
A simple "Hello World" servlet handling GET requests can be implemented as follows. The class is annotated with @WebServlet to map it to a URL pattern, eliminating the need for manual configuration in a deployment descriptor.[33]
java
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/hello")
public class HelloWorldServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h1>Hello, World!</h1>");
out.close();
}
}
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/hello")
public class HelloWorldServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h1>Hello, World!</h1>");
out.close();
}
}
In this example, the doGet() method retrieves the response's PrintWriter to write HTML content directly to the output stream, setting the content type to ensure proper rendering.[33] For POST requests, a similar override of doPost() would process form data or other inputs passed in the request body.[33]
After writing the servlet code, compilation involves using a build tool like Maven to ensure dependencies such as the Jakarta Servlet API (provided by the platform) are resolved.[33] The project is then packaged into a Web Application Archive (WAR) file, which bundles the compiled classes, annotations, and any static resources into a single deployable unit—typically generated in a target directory via a command like mvn clean package.[33] This WAR file serves as the basic distribution format for servlet-based applications.[33]
Configuration and Deployment
Configuration of Jakarta Servlets traditionally relies on the web.xml deployment descriptor file, located in the WEB-INF directory of a web application, to define servlet mappings, URL patterns, and initialization parameters. This XML file uses elements such as <servlet> to declare a servlet by name and class, <servlet-mapping> to associate it with specific URL patterns, and <init-param> to specify parameters accessible via ServletConfig.getInitParameter(). For instance, a servlet named "example" extending HttpServlet can be mapped to the pattern /example/* using <url-pattern>/example/*</url-pattern>, allowing requests matching that path to be routed to the servlet's service method. URL patterns support exact matches (e.g., /foo), path prefixes (e.g., /foo/*), extensions (e.g., *.jsp), and default wildcards (e.g., /*), with matching rules prioritizing exact paths before extensions and then prefixes as outlined in the specification.[34][35]
Since Servlet 3.0, annotations provide an alternative to web.xml for configuration, enabling declarative setup directly in Java code and making the deployment descriptor optional unless metadata-complete="true" is set. The @WebServlet annotation, applied to a class extending HttpServlet, specifies URL patterns via its urlPatterns or value attribute, such as @WebServlet(urlPatterns = {"/hello"}), which maps the servlet to /hello. Initialization parameters can be defined using @WebInitParam(name = "theme", value = "dark") within @WebServlet, overriding or supplementing web.xml entries where both are present, with annotations processed first during container startup. This approach promotes pluggability and reduces XML boilerplate, though web.xml retains precedence for overriding annotation-based configurations in cases of conflict.[34][36][2]
The deployment descriptor's structure follows the web-app schema (e.g., web-app_6_0.xsd for Jakarta EE 10), with a root <web-app> element enclosing sections for servlets, mappings, context parameters via <context-param>, welcome files, error pages, and security constraints. Context parameters, set application-wide with <param-name> and <param-value>, are accessed through ServletContext.getInitParameter(), providing shared configuration like database URLs without servlet-specific scoping. Web fragments in WEB-INF/lib JARs extend this modularity, allowing reusable components to declare their own descriptors in META-INF/web-fragment.xml, merged during deployment with web.xml resolving ambiguities.[37][38][34]
Deployment involves assembling the application into a Web Application Archive (WAR) file, which bundles the WEB-INF directory (containing web.xml, classes, and libraries) with static resources like JSPs and HTML in the root. Tools such as Maven automate this via the war packaging type, compiling sources to WEB-INF/classes and dependencies to WEB-INF/lib, producing a deployable .war file in the target directory after mvn package. The WAR is then deployed to a servlet container, which scans for the descriptor or annotations to configure the application.[39][34]
Hot deployment allows updating applications without full container restarts by placing the WAR in designated directories, such as Tomcat's webapps folder where autoDeploy="true" enables automatic extraction and loading. Context paths, defining the base URI prefix (e.g., /myapp), default to the WAR filename minus the .war extension but can be customized via container-specific configurations like Tomcat's context.xml with a <Context path="/custom"> element. Servlet containers handle the context path during request routing, ensuring the full URI (context path plus servlet path) matches mappings accurately.[40][39][35]
Containers and Runtime
Role of Servlet Containers
Servlet containers serve as the runtime environment for Jakarta Servlets, providing the necessary infrastructure to execute servlets within web applications hosted on web servers or application servers. They handle the communication between clients and servlets by receiving HTTP requests, decoding them into appropriate objects, and routing them to the correct servlet for processing, while also encoding and transmitting responses back to clients.[27]
A core responsibility of servlet containers is the management of the servlet life cycle, which encompasses loading classes, initializing instances via the init() method, invoking the service() method to handle requests, and performing cleanup through the destroy() method when servlets are removed from service. Containers also manage request dispatching, utilizing the RequestDispatcher interface to forward requests to other servlets, JSP pages, or resources, enabling modular request processing within an application. Additionally, they enforce security by implementing authentication mechanisms, authorization checks based on deployment descriptors, and secure transport protocols to protect web resources from unauthorized access. Resource injection is facilitated by the container, particularly through integration with Contexts and Dependency Injection (CDI), allowing automatic injection of dependencies like database connections or configuration objects into servlets to promote reusable and maintainable code.[27][2]
To ensure interoperability, servlet containers must fully comply with the Jakarta Servlet specification, which defines standardized APIs and behaviors for servlet execution, multithreading, and error handling. This compliance extends to support for JavaServer Pages (JSP) technology, where JSP files are dynamically translated into servlets by the container for execution, and web fragments, which allow developers to package reusable subsets of web components with their own descriptors for easier modular deployment in larger applications. Containers vary in scope: full-featured ones, typically embedded in Jakarta EE application servers, provide comprehensive support for the entire platform including enterprise services like persistence and messaging; in contrast, lightweight containers focus exclusively on web-layer technologies such as servlets and JSP, offering simpler, more resource-efficient environments for basic web applications.[27]
Popular Implementations
Apache Tomcat is a widely adopted open-source servlet container developed by the Apache Software Foundation, primarily designed as a standalone server for Jakarta Servlet and Jakarta Server Pages (JSP) technologies. It supports the full Jakarta EE web profile and aligns its versioning with Jakarta EE releases, such as Tomcat 11 implementing Jakarta Servlet 6.1 as part of Jakarta EE 11.[41]
Eclipse Jetty, maintained by the Eclipse Foundation, is a lightweight and embeddable servlet container optimized for modern applications like microservices and cloud-native environments. Its modular architecture allows easy integration into Java applications without requiring a full server setup, while supporting Jakarta Servlet 6.1 in Jetty 12.
WildFly, formerly known as JBoss AS, is a full-featured Jakarta EE application server from Red Hat that integrates Undertow as its high-performance web and servlet container. Undertow provides non-blocking I/O capabilities and supports clustering for high-availability deployments, enabling scalable Jakarta EE applications with features like session replication across nodes. As of November 2025, WildFly Preview versions (e.g., 34+) provide compatibility with Jakarta EE 11 and Servlet 6.1, with full standard support planned in subsequent releases.
All these implementations adhere to the Jakarta Servlet specification, ensuring compatibility with standard web applications.
Advanced Concepts
Filters and Listeners
Filters in Jakarta Servlet provide a mechanism for intercepting and modifying HTTP requests and responses before they reach the target servlet or after the servlet processes them, enabling tasks such as authentication, logging, and data compression without altering the servlet code itself.[42] A filter is implemented by creating a class that implements the jakarta.servlet.Filter interface, which defines three primary methods: init(FilterConfig config) for initialization with configuration parameters, doFilter(ServletRequest request, ServletResponse response, FilterChain chain) for processing the request and response, and destroy() for cleanup upon removal from service. In the doFilter method, pre-processing occurs before invoking the next element in the chain via chain.doFilter(wrappedRequest, wrappedResponse), allowing the filter to inspect or modify the request; post-processing follows this invocation to handle the response similarly.[42]
Filters can be applied to specific URL patterns, servlets, or all resources in a web application, supporting asynchronous operations if declared with asyncSupported=true.[43] Configuration occurs either declaratively in the web.xml deployment descriptor using <filter> and <filter-mapping> elements, where <url-pattern> or <servlet-name> specifies the scope, or programmatically via the @WebFilter annotation on the filter class, which includes attributes like urlPatterns, servletNames, and initParams for initialization parameters.[44] For example, an authentication filter might check user credentials in pre-processing and redirect unauthorized requests, while a logging filter records timestamps and details in post-processing.[42]
When multiple filters apply to a request, they form a chain processed in the order of their declaration in web.xml or annotation processing order, with requests flowing forward through the chain and responses returning in reverse order to enable layered modifications.[43] The container assembles the chain based on matching criteria, prioritizing URL pattern matches over servlet name matches, and supports dispatcher types like REQUEST or FORWARD to control invocation contexts.[42]
Listeners serve as event-driven components that monitor and respond to lifecycle events in the web application, such as context initialization or session creation, without directly handling requests.[45] The ServletContextListener interface, a key example, notifies implementers of application-wide events through its contextInitialized(ServletContextEvent sce) method, called before any servlets or filters are initialized to perform setup tasks like resource allocation, and contextDestroyed(ServletContextEvent sce), invoked after all components are destroyed for cleanup.[46] The ServletContextEvent provides access to the ServletContext for retrieving application attributes or adding components programmatically.[47]
Other listener types include HttpSessionListener for session lifecycle events and ServletRequestListener for request events, but all share the event-driven model defined in the specification.[45] Configuration mirrors filters, using the <listener> element in web.xml to specify the listener class or the @WebListener annotation for declarative setup, with no additional parameters beyond the class implementation.[44] Listeners are invoked in the order of their declaration in web.xml, with context listeners specifically processed sequentially for initialization and in reverse for destruction to ensure proper resource management.[45] This ordering allows coordinated responses to events, such as initializing shared resources during application startup.[48]
Security and Best Practices
Jakarta Servlet applications are susceptible to common web security threats such as Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), and injection attacks, which exploit unvalidated user input and insecure request handling.[49][50] To mitigate XSS, developers must perform context-aware output encoding on all user-supplied data before rendering it in HTML, JavaScript, or other contexts, using libraries like OWASP Java Encoder to escape special characters.[49] CSRF can be prevented by including anti-CSRF tokens in forms and verifying them on the server side, or by leveraging framework-specific synchronizer token patterns integrated with servlet request validation.[50] Injection vulnerabilities, including SQL injection, are addressed through prepared statements and parameterized queries in database interactions initiated from servlets, alongside input validation using whitelists for expected data formats. Enforcing HTTPS for all communications is essential to protect against man-in-the-middle attacks and ensure encrypted transmission of sensitive data.[51]
In version 6.1 (released April 2024), all references to the SecurityManager were removed to align with its deprecation in Java SE 17 and removal in subsequent versions, simplifying security configurations in modern environments.[26]
Role-based security in Jakarta Servlets is implemented declaratively using annotations such as @ServletSecurity to restrict access to servlets or specific HTTP methods based on user roles.[51] For example, @ServletSecurity(@HttpConstraint(rolesAllowed = "ADMIN")) limits invocation to users in the "ADMIN" role, while programmatic checks via HttpServletRequest.isUserInRole("roleName") allow fine-grained authorization within servlet methods.[52] These mechanisms integrate with Jakarta EE's security model, where roles are mapped to users or groups during deployment, ensuring that unauthorized access triggers appropriate HTTP responses like 403 Forbidden.[53]
Best practices for secure servlet development emphasize rigorous input sanitization to reject or cleanse malicious payloads before processing, using validator frameworks like Hibernate Validator or OWASP Java HTML Sanitizer to strip harmful content.[54] Secure session handling involves setting session cookies with HttpOnly and Secure flags to prevent client-side access and ensure transmission over HTTPS only, along with configuring appropriate session timeouts to limit exposure from idle sessions.[55] Developers should avoid deprecated features like the SingleThreadModel interface, which was removed in Servlet 6.0 due to performance inefficiencies and lack of true thread safety, opting instead for thread-safe designs with synchronized access to shared resources.[52] Security filters can be briefly referenced to intercept and validate requests for additional layers of protection, such as CSRF token checks.[53]