DEV Community

Cover image for Introduction to Servlet API and Lifecycle
Arkadipta kundu
Arkadipta kundu

Posted on

Introduction to Servlet API and Lifecycle

In the previous lesson, we explored the intricacies of the HTTP Request-Response cycle, understanding how clients and servers communicate over the web. We learned about HTTP methods, status codes, and the stateless nature of the protocol. Now, we're ready to dive into how Java, specifically the Servlet API, enables servers to actively participate in this cycle, process client requests, and generate dynamic responses. Servlets are the bedrock of Java web application development, forming the foundation upon which powerful frameworks like Spring Boot are built. A deep understanding of Servlets and their lifecycle is absolutely essential for anyone aspiring to become a proficient Java backend engineer, as it demystifies how web applications actually work beneath the surface of higher-level abstractions.

What is a Servlet?

At its core, a Servlet is a Java class that extends the capabilities of a server. It's a server-side component designed to handle requests from web clients (like browsers) and generate dynamic responses. Think of a Servlet as a specialized "worker" program running inside a web server (or more precisely, a web container) that knows how to listen for HTTP requests, interpret them, execute Java code, and then construct an HTTP response to send back to the client.

Unlike regular Java applications that have a main() method and control their own execution, Servlets are managed by a web container (also known as a Servlet container), such as Apache Tomcat, Jetty, or Undertow. The web container is responsible for loading the Servlet, managing its lifecycle, and dispatching incoming requests to the appropriate Servlet instance.

The primary role of a Servlet is to:

  • Receive an HTTP request from a client.
  • Process the request, which might involve reading request parameters, accessing databases, or performing business logic.
  • Generate an HTTP response, typically HTML, but it could also be JSON, XML, or any other data format.
  • Send the response back to the client. This capability to generate dynamic content makes web applications interactive and powerful, moving beyond static HTML pages.

The javax.servlet API

The core functionality of Servlets is defined by a set of interfaces and classes provided in the javax.servlet and javax.servlet.http packages, collectively known as the Servlet API. When you develop a Servlet, you'll be interacting with these components.

Key Interfaces:

Servlet Interface: This is the most fundamental interface in the Servlet API. All Servlets, directly or indirectly, must implement this interface. It defines the core methods that the web container uses to manage the Servlet's lifecycle and handle requests:

  • void init(ServletConfig config): Called once by the Servlet container to initialize the Servlet.
  • void service(ServletRequest req, ServletResponse res): Called by the container for each request to the Servlet.
  • void destroy(): Called once by the container before the Servlet is removed from service.
  • ServletConfig getServletConfig(): Returns a ServletConfig object, which contains initialization parameters for the Servlet.
  • String getServletInfo(): Returns a String containing information about the Servlet, such as its author, version, and copyright.

ServletRequest and ServletResponse Interfaces: These interfaces represent the client request and server response, respectively.

  • ServletRequest: Provides methods to retrieve information about the client request, such as parameters, headers, and input streams.
  • ServletResponse: Provides methods to send a response back to the client, such as setting content type, status codes, and writing output.

ServletConfig Interface: An object of this type is passed to the init() method. It provides access to initialization parameters specific to that Servlet, as defined in the deployment descriptor (which we'll cover later) or annotations. It also provides access to the ServletContext.

ServletContext Interface: This interface defines a set of methods that a Servlet uses to communicate with its Servlet container. It's unique per web application and provides common resources and configuration for all Servlets within that application, such as context-wide initialization parameters and logging facilities. (We'll explore ServletContext in more detail in later lessons).

Helper Classes:

While you can implement the Servlet interface directly, it's often more convenient and standard to extend one of the abstract classes provided by the API:

GenericServlet: This is an abstract class that implements the Servlet, ServletConfig, and Serializable interfaces. It provides default implementations for init(), destroy(), getServletConfig(), and getServletInfo(). However, it leaves the service() method abstract, meaning you still have to implement the service() method yourself. GenericServlet is protocol-independent, meaning it can handle any type of request (though it's rarely used for HTTP directly).

HttpServlet: This is an abstract class that extends GenericServlet and is specifically designed to handle HTTP requests. It provides convenient implementations for the service() method that automatically dispatches requests to specialized methods based on the HTTP method of the request. For example:

  • doGet(HttpServletRequest req, HttpServletResponse resp): Handles HTTP GET requests.
  • doPost(HttpServletRequest req, HttpServletResponse resp): Handles HTTP POST requests.
  • doPut(HttpServletRequest req, HttpServletResponse resp): Handles HTTP PUT requests.
  • doDelete(HttpServletRequest req, HttpServletResponse resp): Handles HTTP DELETE requests.
  • And others like doHead, doOptions, doTrace. This means that when you write an HTTP Servlet, you typically extend HttpServlet and override the doGet(), doPost(), or other doXxx() methods relevant to your application, rather than implementing the service() method directly. The HttpServletRequest and HttpServletResponse objects passed to these methods are HTTP-specific sub-interfaces of ServletRequest and ServletResponse, providing additional methods tailored for HTTP communication.

Real-World Example: When building a login page, you'd typically handle the initial page display with doGet() and the form submission with doPost(). Hypothetical Scenario: Imagine a Servlet designed to display a user's profile. A user navigating to /profile would trigger a doGet() request, where the Servlet would fetch user data from a database and render an HTML page. If the user then submits a form to update their profile picture, a doPost() or doPut() request would be sent, and the Servlet's corresponding method would handle the file upload and database update.

The Servlet Lifecycle

Understanding the Servlet lifecycle is crucial because it dictates when and how your Servlet's methods are invoked by the web container. This knowledge helps you properly manage resources, initialize components, and ensure efficient request processing. The lifecycle describes the sequence of events from when a Servlet is loaded into memory until it is removed from service.

A Servlet typically goes through four main phases:

1. Loading and Instantiation

When it happens:

  • First Request: The container usually loads and instantiates a Servlet class the first time a request for that Servlet arrives.
  • Server Startup: Optionally, you can configure the container to load and instantiate a Servlet when the web application starts up (e.g., for Servlets that need to perform immediate initialization or are frequently accessed). What happens:
  • The web container locates the Servlet class (e.g., MyServlet.class).
  • It loads the class into the Java Virtual Machine (JVM).
  • It creates an instance of the Servlet class using its default (no-argument) constructor. Only one instance of a Servlet class is typically created per web application by the container. This single instance will handle all subsequent requests. Real-World Example: When a user first navigates to /mywebapp/products, the ProductListServlet might be loaded and instantiated to serve that initial request. Hypothetical Scenario: An analytics Servlet that counts unique visitors might be configured to load on startup, so it's immediately ready to begin tracking requests without any initial delay.

2. Initialization (init() method)

When it happens: Immediately after the Servlet has been instantiated. The init() method is guaranteed to be called only once throughout the Servlet's lifetime.
Purpose: This phase is used for one-time setup tasks. Any resources that need to be initialized once and then shared across multiple requests should be set up here.
Method Signature: public void init(ServletConfig config) throws ServletException
ServletConfig: The web container passes a ServletConfig object to the init() method. This object allows the Servlet to access configuration parameters that are specific to itself and provides access to the ServletContext.
Common tasks:

  • Establishing database connections (e.g., creating a connection pool).
  • Loading configuration files.
  • Initializing logging mechanisms.
  • Any expensive operations that shouldn't be performed for every request. Real-World Example: A Servlet that needs to connect to a database could establish its DataSource (a factory for database connections) within the init() method, ensuring the connection pool is ready before any requests arrive. Hypothetical Scenario: A content management system's ImageResizerServlet might load image processing libraries or define a temporary directory for resized images during its init() phase.

3. Request Handling (service() method / doXxx() methods)

When it happens: For every client request that targets this Servlet. This method can be called multiple times concurrently by different threads, each handling a separate request.
Purpose: This is where the core logic of the Servlet resides. It processes the incoming request and generates a response.
Method Signature: public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException (for Servlet and GenericServlet)
HttpServlet's role: As discussed, if you extend HttpServlet, its service() method automatically inspects the HTTP method of the incoming request (e.g., GET, POST) and dispatches the request to the appropriate doGet(), doPost(), etc., method that you've overridden. This makes your code cleaner and more organized.
ServletRequest and ServletResponse (or HttpServletRequest and HttpServletResponse): For each request, the container creates new ServletRequest and ServletResponse objects (or their HTTP-specific counterparts) and passes them to the service() method. These objects contain all the information about the current request and provide methods to construct the response.
Real-World Example: A ProductServlet receives a GET request for /products/123. The service() method (or doGet() in HttpServlet) extracts the product ID, queries the database, retrieves product details, and then generates an HTML page or JSON data with the product information. Hypothetical Scenario: A chat application's ChatServlet receives a POST request containing a new message. Its doPost() method would extract the message and sender, store it in a message queue or database, and then send a confirmation response back to the client.

4. Destruction (destroy() method)

When it happens: When the web application is shut down, undeployed, or the Servlet is no longer needed by the container. Like init(), the destroy() method is guaranteed to be called only once per Servlet instance.
Purpose: This phase is used for cleanup tasks, releasing resources that were acquired during the init() phase or throughout the Servlet's operation.
Method Signature: public void destroy()
Common tasks:

  • Closing database connections or connection pools.
  • Saving any persistent state.
  • Releasing file handles or other system resources.
  • Deregistering from event listeners. Real-World Example: If a Servlet maintained a special cache in memory, the destroy() method would be the place to flush that cache to persistent storage before the application shuts down. Hypothetical Scenario: An AuditLogServlet might have an open file handle to write audit events. In its destroy() method, it would ensure all pending logs are written and the file handle is properly closed to prevent data loss or resource leaks.

The Single Instance Model and Thread Safety

A crucial aspect of the Servlet lifecycle is the single instance model. For a given Servlet class, the web container typically creates only one instance within a web application. This single instance handles all concurrent requests.

This design has significant implications for thread safety:

  • Instance variables (fields) of a Servlet are shared across all requests. If you store data in instance variables, and multiple requests try to modify that data concurrently, you could run into race conditions and inconsistent states.
  • Local variables within service() or doXxx() methods are thread-safe because each thread gets its own copy of these variables.
  • Therefore, when designing Servlets, it's a best practice to:

    • Avoid using instance variables to store client-specific data.
    • Make instance variables read-only or ensure they are properly synchronized if they must be mutable and shared.
    • Prefer local variables for data that is specific to a single request.

This single instance, multi-threaded model is highly efficient as it avoids the overhead of creating new Servlet objects for every request, but it demands careful attention to thread safety.

Practical Examples and Demonstrations

Let's illustrate the Servlet API and lifecycle with a simple HttpServlet. We'll use System.out.println statements to trace when each lifecycle method is invoked.

java

package com.example.web;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet; // This annotation is for configuration, but we'll focus on methods for now.
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date; // To show when the request was processed
// Note: The @WebServlet annotation is a modern way to configure Servlets.
// We will cover configuration in detail in a later lesson.
// For now, imagine this Servlet is mapped to the URL path "/lifecycle-demo".
public class LifecycleDemoServlet extends HttpServlet {
    private int hitCount; // An instance variable to demonstrate shared state
    /**
     * Called by the Servlet container to indicate that the Servlet is being placed into service.
     * This method is guaranteed to be called only once during the Servlet's lifetime.
     * It's suitable for one-time initialization tasks.
     * @param config The ServletConfig object containing the Servlet's configuration and initialization parameters.
     * @throws ServletException If an exception occurs that prevents the Servlet from being initialized.
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        // Always call super.init(config) if overriding init() in HttpServlet
        // to ensure proper initialization of the parent class.
        super.init(config);
        hitCount = 0; // Initialize a counter for demonstration
        System.out.println("LifecycleDemoServlet: init() method called. Servlet initialized at " + new Date());
        System.out.println("  - Servlet Name from config: " + config.getServletName());
        // We could load other resources here, like database connections, etc.
    }
    /**
     * Called by the Servlet container to allow the Servlet to respond to a GET request.
     * This method is invoked for every GET request targeting this Servlet.
     * It uses HttpServletRequest to read request data and HttpServletResponse to write response data.
     * @param request The HttpServletRequest object containing client request information.
     * @param response The HttpServletResponse object used to send the response back to the client.
     * @throws ServletException If a Servlet-specific error occurs.
     * @throws IOException If an I/O error occurs while processing the request.
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        hitCount++; // Increment the counter with each GET request
        System.out.println("LifecycleDemoServlet: doGet() method called. Request received at " + new Date());
        System.out.println("  - Current hit count: " + hitCount);
        // Set the content type of the response
        response.setContentType("text/html");
        // Get a PrintWriter to write the response body
        PrintWriter out = response.getWriter();
        // Write HTML content to the response
        out.println("<html>");
        out.println("<head><title>Servlet Lifecycle Demo</title></head>");
        out.println("<body>");
        out.println("<h1>Hello from LifecycleDemoServlet!</h1>");
        out.println("<p>This servlet has been accessed <strong>" + hitCount + "</strong> times since initialization.</p>");
        out.println("<p>Current Time: " + new Date() + "</p>");
        out.println("</body>");
        out.println("</html>");
        out.close(); // Close the writer
    }
    /**
     * Called by the Servlet container to indicate that the Servlet is being removed from service.
     * This method is guaranteed to be called only once during the Servlet's lifetime,
     * just before the Servlet instance is garbage collected.
     * It's suitable for cleanup tasks, such as closing database connections or saving state.
     */
    @Override
    public void destroy() {
        System.out.println("LifecycleDemoServlet: destroy() method called. Servlet being destroyed at " + new Date());
        System.out.println("  - Final hit count recorded: " + hitCount);
        // Here we would release any resources initialized in init(), e.g., close DB connections.
        super.destroy(); // Call super.destroy() to ensure proper cleanup by parent classes.
    }
    /**
     * Returns a String containing information about the Servlet.
     * @return A String containing information about the Servlet.
     */
    @Override
    public String getServletInfo() {
        return "A simple Servlet to demonstrate lifecycle methods.";
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation of the example:

  • LifecycleDemoServlet extends HttpServlet: This is the standard practice for web Servlets, leveraging the HTTP-specific handling.
  • hitCount instance variable: We introduce a simple int variable to demonstrate that instance variables persist across requests for the same Servlet instance.
  • init(ServletConfig config):
    • This method prints a message indicating its invocation and the current time.
    • hitCount is initialized to 0 here, ensuring it starts fresh when the Servlet is brought into service.
    • It retrieves the Servlet's name from the ServletConfig object, demonstrating how to access initialization information.
  • doGet(HttpServletRequest request, HttpServletResponse response):
    • This method is called for every HTTP GET request.
    • hitCount is incremented. If you refresh your browser multiple times, you'll see this counter increase, proving that the same Servlet instance is handling successive requests.
    • It sets the response ContentType to text/html and obtains a PrintWriter to write HTML directly to the browser.
    • It displays a message including the current hitCount and the server's current time.
  • destroy():
    • This method prints a message when the Servlet is about to be removed from service.
    • It also shows the final hitCount, demonstrating that the instance variable retained its state until destruction.
  • getServletInfo(): Provides a simple descriptive string about the Servlet.

How to observe the lifecycle:

To truly observe this, you would compile this Servlet, package it into a .war file, and deploy it to a web container like Tomcat.

  • Start Tomcat: You'll see no output from init() initially unless the Servlet is configured for "load-on-startup".
  • First browser request: Access http://localhost:8080/your_context_root/lifecycle-demo.
  • In your Tomcat console, you will see the init() method output, followed by the doGet() method output.
  • The browser will display the "Hello" message with "1" hit.
  • Subsequent browser requests (refresh the page):
    • In your Tomcat console, you will only see the doGet() method output. The init() method is not called again.
    • The hitCount in the browser will continue to increment.
  • Stop Tomcat / Undeploy the web application: - In your Tomcat console, you will see the destroy() method output, indicating the Servlet is being taken out of service. This example clearly demonstrates that init() and destroy() are called once per Servlet instance, while doGet() (or service()) is called for every request, and the Servlet instance persists state (hitCount) between requests.

Exercises

Trace the Output: Imagine you deploy the LifecycleDemoServlet and perform the following actions:

  • Start Tomcat.
  • Open browser, go to /lifecycle-demo.
  • Refresh browser, go to /lifecycle-demo.
  • Open another browser tab/window, go to /lifecycle-demo.
  • Stop Tomcat. Write down the exact sequence of System.out.println messages you would expect to see in the Tomcat console, including which method is called and the hitCount at each stage.

Modify the Servlet:

  • Modify the LifecycleDemoServlet to include a doPost method. In this method, simply print a message like "LifecycleDemoServlet: doPost() method called." to the console.
  • Explain what would happen if a client sent an HTTP POST request to this Servlet. Which methods would be invoked in the Servlet? How would the hitCount behave? (You don't need to create an actual form yet, just conceptually explain the invocation flow.)

Resource Management Analogy:

  • Think about a real-world resource that needs to be acquired once, used many times, and then properly released. For example, a physical library card, a specific tool in a workshop, or opening a bank account.
  • Describe how the init(), service(), and destroy() methods of the Servlet lifecycle map to the lifecycle of this real-world resource, explaining what actions would be performed in each stage for your chosen analogy.

Conclusion

In this lesson, we've laid the critical groundwork for understanding Java web applications by exploring the Servlet API and its fundamental lifecycle. We defined what a Servlet is, examined the key interfaces and classes (Servlet, HttpServlet, ServletConfig, ServletRequest, ServletResponse) that form its API, and most importantly, delved into the Servlet's lifecycle: instantiation, initialization (init()), request handling (service()/doXxx()), and destruction (destroy()).

We emphasized the single instance model of Servlets and the implications for managing shared state and ensuring thread safety. Through practical examples, you've seen how these lifecycle methods are invoked by the web container at specific points, allowing you to perform one-time setup, process individual requests, and execute cleanup tasks.

This foundational knowledge is absolutely crucial as you progress towards more advanced topics and frameworks like Spring Boot. Spring Boot applications, at their core, utilize Servlets (or their reactive counterparts) to handle web requests. Understanding the underlying Servlet mechanism will enable you to grasp how Spring's DispatcherServlet (a central component we'll learn about later) integrates with the web container and manages the flow of requests.

In the next lesson, we'll take this theoretical understanding and apply it to develop our very first functional "Hello World" Servlet application. You'll learn how to compile and deploy a simple Servlet, observe its execution in a web container, and truly see these concepts come to life.

Top comments (0)