Servlet Series – Chapter 3: Servlet Lifecycle and Request Processing

Rohit GawandeRohit Gawande
23 min read

Full Stack Java Development Series

This chapter is part of the Servlet Series, which itself forms a core segment of my Full Stack Java Development learning journey. In the previous chapter, we focused on the basic setup and running of a servlet. Now, in Chapter 3, we move deeper into the heart of servlet execution — understanding how Tomcat deploys applications, how requests are processed internally, and how the servlet lifecycle unfolds from loading to de-instantiation.

By the end of this chapter, you will have a clear, step-by-step understanding of:

  • How Tomcat identifies and deploys your web applications.

  • The exact sequence of events that occur when a client sends a request.

  • The lifecycle phases of a servlet and their significance.

  • How to optimize servlet performance using configuration options like load-on-startup.

  • The difference between implementing Servlet directly and extending GenericServlet for cleaner, more maintainable code.

Section 1 – Understanding the Deployment Folder in Tomcat

When working with Java Servlets inside Apache Tomcat, the webapps directory plays a very special role. This folder is often referred to as the deployment folder, and its significance becomes clear as soon as you start the server.

Imagine you own a large library. Every morning, before opening to the public, you send your assistants to check all the shelves, identify every available book, and place them neatly on the display racks so readers can access them instantly. Tomcat follows the same routine when it starts — only instead of books, it deals with web applications.

As soon as the Tomcat server starts, it automatically scans the webapps folder. This folder contains all the web applications you have developed and deployed. Tomcat then copies each project into its execution area — a space where the application is “ready” to handle client requests. This process is what we mean by deployment.

From that moment, the applications are no longer just files in your folder; they are live applications within Tomcat, prepared to provide services whenever a request arrives.


Why the webapps folder is called the Deployment Folder

Tomcat’s design is intentional. It is programmed to automatically check how many projects are present in the webapps folder and deploy each one without manual intervention. Every single project it finds gets placed into the Tomcat execution area.

This is why we call the webapps folder the deployment folder — it’s the central location where you place your web applications so Tomcat can pick them up and make them operational.

If you think of a web application like a phone, then placing it in webapps is like switching the phone on and registering it with the network — once it’s registered, you can make and receive calls (or in our case, handle requests and send responses).


ServletContext Creation for Each Project

Once Tomcat loads all the projects into the execution area, it does something important behind the scenes: it creates a separate ServletContext object for each project.

This ServletContext acts like the project’s personal assistant. It holds configuration information, helps with resource management, and acts as a bridge between the servlet and the Tomcat container. No two projects share the same ServletContext — each project gets its own isolated instance.


Scanning the web.xml File

After creating the ServletContext object, Tomcat also reads the web.xml deployment descriptor file associated with the project. This file contains important mappings between URL patterns and the corresponding servlets. Tomcat stores this mapping information for later use, so when a request arrives, it knows exactly which servlet should handle it.

For example, if web.xml defines that the /demo URL pattern should be handled by SecondServlet, Tomcat remembers this. Later, when the client sends a request to /demo, Tomcat will instantly direct the request to SecondServlet without scanning the file again.


In short, the moment you start Tomcat:

  1. It scans webapps (deployment folder).

  2. Deploys each project into the execution area.

  3. Creates a separate ServletContext for each project.

  4. Reads and stores all URL pattern mappings from web.xml.


If you imagine the server as a stage performance, the deployment process is like the pre-show setup: the stage is prepared, the actors (servlets) are placed backstage, the lighting is tested, and the script (web.xml) is read so that once the audience (clients) arrive, the performance can begin immediately.


Section 2 – Sending a Request to a Web Application

The moment Tomcat finishes scanning the deployment folder and moves your projects into its execution area, it prepares each application to participate in request handling. For every deployed project, the engine creates a dedicated ServletContext object. This context is unique per application and represents the shared runtime environment for all servlets in that application. At the same time, Tomcat reads the application’s web.xml and stores the URL patterns for all dynamic resources so that future requests can be routed without re-parsing configuration on every hit. This preloading of the deployment descriptor is essential for quick dispatching when the first request arrives.

Figure 1: End-to-end request flow in Tomcat showing browser → HTTP request → execution area → Catalina container → servlet lifecycle (loading, instantiation, initialization, request processing, de-instantiation).

The diagram captures the full path of a typical GET request starting in the browser and ending as an HTTP response rendered in the client. The request begins when the user types or clicks a URL. Tomcat is already running on a port, the execution area is active, and the application’s context is registered. The browser connects to the server and the HTTP protocol layer constructs an HttpServletRequest object. In the case of a GET request, there is no body section, so the request is composed of a request line and headers. This object travels to the Tomcat engine, which extracts two critical pieces of information: the context name that identifies the target application and the URL pattern that identifies the dynamic resource. Because these mappings were read from web.xml at startup, Tomcat can immediately choose the correct servlet without further file I/O.

To send a request to any application, the client must know the URL pattern and the application’s context root. The general form is [http://localhost:[port]/[NameOfTheApplication](http://localhost:%5Bport%5D/%5BNameOfTheApplication) or ContextRoot]/[url_pattern of the resource]. For example, when the browser requests http://localhost:9999/SecondApp/demo, Tomcat associates SecondApp with the deployed application and /demo with the servlet mapped to that pattern. This association was derived from the application’s web.xml or from annotations during server startup. Having identified the dynamic resource, Tomcat hands control to its servlet container, Catalina, which is responsible for executing servlet lifecycle actions. At this point, the ServletContext—an interface whose implementation is provided by the container—serves as the application’s shared backdrop, and the container proceeds to locate and run the specific servlet that matches /demo, which in this case is SecondServlet.

Inside the container, the lifecycle of the target servlet unfolds in a well-defined sequence. If this is the first time the servlet is needed, the container loads the .class into memory, creates an instance of the servlet class, and initializes it by calling init with a ServletConfig. These actions occur automatically and only once per servlet instance, so subsequent requests do not repeat the initial phases. The following statements express these actions in code form, reflecting what the container effectively performs behind the scenes:

// 1. Servlet Loading (.class file loading)
Class c = Class.forName("SecondServlet");
// 2. Servlet Instantiation (for loaded class create an Object)
SecondServlet obj = (SecondServlet) c.newInstance();
// newInstance expects the class to be public and to have a no-argument constructor.
// If this expectation is not met, the object will not be created.
// 3. Servlet Initialization (for the created object inject the required values)
obj.init(ServletConfig config);
// The container automatically creates and supplies the config object (dependency injection).

With the servlet initialized, the request processing phase begins. The container creates the request and response objects and invokes the servlet’s service method on the live instance. Reading input sent by the client always happens through the request object, and writing output for the browser always happens through the response object, which exposes a PrintWriter for sending bytes back to the client as the response body. If a developer uses System.out.println during request processing, the text appears in Tomcat’s console rather than in the browser because console output is not part of the HTTP response. To deliver content to the client, the application must write into the ServletResponse, optionally setting the content type so that the browser knows how to interpret the payload.

// 4. Request Processing phase (for client request this method will be called)
obj.service(ServletRequest request, ServletResponse response);
// Two objects are created and passed: request (input from client) and response (output to browser).
// Use 'request' to read data and 'response' to write data via PrintWriter.

During this exchange, the container composes an HttpServletResponse populated with a status line such as 200 OK, response headers including a content type like text/html, and the actual response body produced by the servlet. The browser first inspects the headers to understand the payload type and then renders the body accordingly. The network channel is then closed, which means both the request and response objects become unreachable and their lifetimes end with the completion of the transaction. Because the connection ends and no server-side conversational state is retained by default, HTTP is described as a stateless protocol. When the client sends another request—even to the same URL—the entire sequence repeats, with the important distinction that loading, instantiation, and initialization are skipped if the servlet instance already exists, so only the request processing method is executed.

Eventually there are moments when a servlet must be taken out of service. This can happen when the server stops, the application is undeployed, or the resource remains idle and the container decides to reclaim it. In such cases, Catalina triggers the de-instantiation event by calling destroy on the servlet instance. The container tears down the servlet, and the objects that supported it are signaled for cleanup. The following line represents that final lifecycle callback:

// 5. Servlet De-Instantiation event
obj.destroy();
// When destroy() is called, the servlet instance is de-instantiated,
// associated resources such as the config are released,
// and the container reclaims the region that held the loaded bytecode.

Returning to the diagram, the left side shows the browser sending a GET request to /SecondApp/demo, producing an HttpRequest with a request line and request headers. The request travels to the execution area where Tomcat identifies the context and URL pattern, then forwards control to the container. The right side of the figure shows the container’s lifecycle track: loading, instantiation, initialization with ServletConfig, and the request processing stage where service(request, response) runs. The response path mirrors the request path in reverse, carrying back an HttpResponse with a status line of 200 OK, a header indicating text/html, and a body that holds the actual output. The caption’s emphasis on the channel being cut after the response illustrates the stateless nature of HTTP and explains why each new request requires a fresh HttpServletRequest and HttpServletResponse.

Putting it all together, Tomcat first prepares applications by creating a separate ServletContext for each project and preloading the mappings from web.xml. A client then sends a request using a URL that includes the application’s context root and the servlet’s URL pattern. The HTTP layer constructs the request object and hands it to Tomcat, which extracts the context and pattern and delegates execution to Catalina. The container manages the servlet’s lifecycle by loading, instantiating, initializing, and finally invoking the service method with fresh request and response objects. The response travels back to the browser as a complete HTTP message, and the connection closes, leaving no built-in memory of the conversation. When the server stops or the application is undeployed, the container completes the lifecycle by calling destroy, allowing the runtime to release resources cleanly. This complete circuit—from URL entry to response rendering and eventual teardown—is precisely what the diagram depicts and what you should internalize as the foundational theory of servlet request processing in Tomcat.

Here’s a short, crisp summary you can add at the end of your explanation for intervie

Summary:
The servlet lifecycle inside a container follows a fixed sequence:

  1. Loading – The .class file is loaded into memory when the servlet is first needed.

  2. Instantiation – The container creates one object of the servlet class.

  3. Initializationinit(ServletConfig) is called once to inject configuration.

  4. Request Processing – For each client request, the container creates HttpServletRequest and HttpServletResponse objects and calls service() to handle input/output.

  5. Destroy – When the servlet is removed from service, destroy() is called to release resources.
    HTTP being stateless means each request/response pair is independent, and initialization happens only once per servlet instance.

Section 5 – Deployment and Compilation Steps


Deployment and Compilation Steps

Before a servlet can handle any client requests, its compiled .class file must be placed in the correct location inside the web application directory structure. In Tomcat, the compiled servlet should be stored inside the WEB-INF/classes folder of the application. This is the designated location where the server searches for compiled Java classes at runtime.

Once the servlet’s .class file is in place, you need to compile it with the servlet-api.jar library in the classpath, because this JAR contains the Servlet API classes required for compilation. Without it, your code referencing javax.servlet.* will fail to compile.

The compilation can be done directly from the command line as follows:

C:\Tomcat 9.0\webapps\SecondApp>
javac -cp "C:\Tomcat 9.0\lib\servlet-api.jar";. SecondServlet.java

In this example:

  • -cp specifies the classpath to include the servlet-api.jar.

  • The . (dot) tells the compiler to also look for source files in the current directory.

  • SecondServlet.java is the source file being compiled.


Once compiled and deployed in the WEB-INF/classes directory, the servlet can be executed. Below is the full implementation of the SecondServlet class:

import java.io.*;
import javax.servlet.*;

public class SecondServlet implements Servlet {

    static {
        System.out.println(":SecondServlet .class file is loading...");
    }

    public SecondServlet() {
        System.out.println("SecondServlet Object is instantiated..");
    }

    // For servlet initialization, the container calls this method
    public void init(ServletConfig config) throws ServletException {
        System.out.println("Servlet initialization ");
    }

    public ServletConfig getServletConfig() {
        return null;
    }

    // Request Processing Logic
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        System.out.println("Servlet Request Processing...");
        PrintWriter out = response.getWriter();
        out.println("<html><head><title>Output</title></head>");
        out.println("<body>");
        out.println("<marquee><h1 style='color:red;'>Hi, I am Rohit Gawande</h1></marquee>");
        out.println("<marquee>Welcome TO ADV JAVA</marquee>");
        out.println("</body>");
        out.println("</html>");
        out.close();
    }

    public String getServletInfo() {
        return null;
    }

    // Servlet De-Instantiation Logic
    public void destroy() {
        System.out.println("Servlet De-Instantiation ... ");
    }
}

To map this servlet in the application, the web.xml deployment descriptor is configured as follows:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <servlet>
        <servlet-name>DemoServlet</servlet-name>
        <servlet-class>SecondServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>DemoServlet</servlet-name>
        <url-pattern>/demo</url-pattern>
    </servlet-mapping>

</web-app>

With this configuration:

  • The servlet is identified by the name DemoServlet.

  • The class SecondServlet will be executed when the client requests the /demo URL pattern.


Execution and Observations

When the server receives the first request to /demo, the following output appears on the server console:

SecondServlet.class file is loading...
SecondServlet Object is instantiated...
Servlet initialization...
Servlet Request Processing

For the second request, the output changes to:

Servlet Request Processing

This difference occurs because:

  • In the first request, the servlet lifecycle starts from the beginning — loading the class, instantiating the object, initializing it, and then processing the request.

  • In the second request, the servlet is already loaded, instantiated, and initialized, so Tomcat directly executes the request processing logic (service() method).

This explains why the processing time for the second request is significantly faster.


Section 6 – Maintaining Uniform Response Time


Maintaining Uniform Response Time

From our earlier observation, we noticed a clear difference between the first request and subsequent requests made to a servlet. The first request always takes slightly longer because the container has to perform the initial class loading, object instantiation, and initialization before it can begin request processing. All subsequent requests bypass these steps and directly invoke the service() method, resulting in a much faster response.

While this difference may seem negligible in a small application, in a production environment — especially under high load — the first-request delay can cause noticeable latency. This becomes even more critical when the first request is coming from a real user right after a server restart or deployment.

So, the question is: how do we ensure that all requests, including the very first one, have the same quick response time?


The Solution – Preloading the Servlet

To achieve uniform response time, we can instruct the Tomcat engine to load, instantiate, and initialize the servlet at the time of server startup — even before the first client request arrives. This process is known as preloading or eager loading of a servlet.

Tomcat allows servlet preloading through two configuration approaches:

  1. XML configuration using the <load-on-startup> tag inside web.xml.

  2. Annotation-based configuration using the loadOnStartup attribute of @WebServlet.


1. Using <load-on-startup> in web.xml

By adding the <load-on-startup> tag inside the servlet configuration in web.xml and assigning it any positive integer value, we tell Tomcat to initialize that servlet during server startup.

Here’s the XML configuration:

<web-app>

    <servlet>
        <servlet-name>DemoServlet</servlet-name>
        <servlet-class>SecondServlet</servlet-class>
        <load-on-startup>10</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>DemoServlet</servlet-name>
        <url-pattern>/demo</url-pattern>
    </servlet-mapping>

</web-app>

How it works:

  • The <load-on-startup> value determines the order of loading if multiple servlets are configured.

  • A smaller number means higher priority for loading.

  • In this example, 10 ensures that the servlet loads during startup, but after any servlet with a value less than 10.


What Happens at Server Startup with Preloading

With the above configuration, when Tomcat starts:

SecondServlet.class file is loading...
SecondServlet Object is instantiated...
Servlet initialization...

This means that before any request is made, the servlet is already in memory, ready to handle requests instantly.

Now, when a request comes in — whether it’s the first request or the hundredth — only the request processing phase is executed:

Output for first request:

Servlet Request Processing

Output for second request:

Servlet Request Processing...

Both have the same performance because the expensive initialization steps are done ahead of time.


2. Using Annotation-Based Configuration

If you prefer annotation-based configuration instead of XML, the same effect can be achieved using @WebServlet with the loadOnStartup attribute:

@WebServlet(urlPatterns = "/test", loadOnStartup = 10)
public class SecondServlet implements Servlet {
    // Implementation here
}

This approach eliminates the need to define the servlet mapping in web.xml, making the code more concise while still ensuring uniform response time.


With this technique, you effectively remove the first-request performance penalty. It’s a best practice for servlets that are critical to application performance and are expected to handle requests immediately after server startup.


Section 7 – Example: ThirdServlet using Annotations


Example: ThirdServlet using Annotations

In the previous section, we learned how to use @WebServlet with loadOnStartup to preload a servlet at server startup, ensuring uniform response times.
Now, let’s create a complete working servlet example using annotations instead of XML configuration.


Annotation Mapping vs. XML Mapping

The following annotation at the top of the servlet class:

@WebServlet(urlPatterns = "/test", loadOnStartup = 10)

is equivalent to the following web.xml configuration:

<servlet>
    <servlet-name>DemoServlet</servlet-name>
    <servlet-class>SecondServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>DemoServlet</servlet-name>
    <url-pattern>/test</url-pattern>
</servlet-mapping>

This annotation eliminates the need to manually declare the servlet in web.xml, making the code more concise and easier to maintain.


Full Java Code – ThirdServlet Implementing the Servlet Interface

import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;

@WebServlet(urlPatterns = "/test", loadOnStartup = 10)
public class ThirdServlet implements Servlet {

    static {
        System.out.println("ThirdServlet.class file is loading...");
    }

    public ThirdServlet() {
        System.out.println("ThirdServlet Object is instantiated..");
    }

    // Servlet Initialization
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("Servlet initialization");
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    // Request Processing Logic
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        System.out.println("Servlet Request Processing...");

        PrintWriter out = response.getWriter();
        out.println("<html><head><title>Output</title></head>");
        out.println("<body>");
        out.println("<marquee><h1 style='color:red;'>Hi, I am Rohit Gawande</h1></marquee>");
        out.println("<marquee>Working with Annotation</marquee>");
        out.println("</body>");
        out.println("</html>");
        out.close();
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    // Servlet De-Instantiation Logic
    @Override
    public void destroy() {
        System.out.println("Servlet De-Instantiation...");
    }
}

Lifecycle Flow for ThirdServlet

With loadOnStartup = 10, here’s what happens:

At server startup:

ThirdServlet.class file is loading...
ThirdServlet Object is instantiated..
Servlet initialization

When a request arrives:

Servlet Request Processing...
  • The HTML response is sent back to the browser, displaying a red marquee message.

When the server shuts down or the servlet is removed:

Servlet De-Instantiation...

Limitations of Directly Implementing the Servlet Interface

While this example works, directly implementing the Servlet interface has some drawbacks:

  1. Too much boilerplate code – You must implement all five methods (init, service, destroy, getServletConfig, getServletInfo), even if some are not needed.

  2. No default implementations – Every method must be explicitly coded.

  3. Better alternative available – Using GenericServlet or HttpServlet reduces boilerplate and provides default implementations for most methods.


This example shows how to fully configure and run a servlet with annotations, but in real-world projects, developers usually extend HttpServlet to simplify the implementation.


8. Moving to GenericServlet – Simplifying Servlet Development

When we first start working with servlets, it’s common to begin by directly implementing the Servlet interface. At first glance, this might seem like a natural approach — after all, the interface defines the methods a servlet must support, and we simply need to implement them. But soon, an important limitation becomes apparent.

The only logic a developer is truly required to write for a servlet is the request processing code — the part that determines how the servlet responds to a client request. This logic resides inside the service() method. All other lifecycle methods such as init(), destroy(), getServletConfig(), and getServletInfo() are typically handled by the container and don’t always require custom behavior.

However, if you choose to implement the Servlet interface directly, the rules of Java interfaces dictate that you must provide a concrete implementation for all of its methods — even if your implementations are just empty bodies. This leads to unnecessary boilerplate code, which:

  • Increases the length of the servlet class.

  • Decreases overall readability.

  • Adds maintenance overhead for methods you might never use.

From a development perspective, this is inefficient. It’s like being forced to fill out every field in a lengthy form, even if most of them are irrelevant to you.


The Solution: GenericServlet

To solve this problem, the designers of the Java Servlet API introduced the GenericServlet class. This class follows the Adapter Class Design Pattern, where an abstract class implements an interface and provides default (empty) implementations for most methods, leaving only the essential one as abstract.

The GenericServlet class:

  • Already implements the Servlet interface.

  • Provides concrete implementations for all methods except service().

  • Leaves service() as abstract, which means you only need to implement this one method in your servlet.

This drastically reduces boilerplate code and improves readability. With GenericServlet, your servlet focuses purely on request processing logic — the part that matters most.


How GenericServlet is Defined

The source structure of GenericServlet looks like this:

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    public GenericServlet();
    public void init(ServletConfig config) throws javax.servlet.ServletException;
    public void destroy();
    public ServletConfig getServletConfig();
    public ServletContext getServletContext();
    public String getServletInfo();
    public void init() throws javax.servlet.ServletException;
    public abstract void service(ServletRequest req, ServletResponse resp)
        throws ServletException, IOException;
    public java.lang.String getServletName();
}

Notice a few key details:

  1. The class is marked abstract because it contains the abstract service() method.

  2. Every other method from the Servlet interface already has an implementation here.

  3. There are two versions of init() — one that accepts a ServletConfig object and one that does not. The no-argument version is particularly useful for developers, as it allows initialization without dealing directly with the ServletConfig parameter.

  4. The default content type for responses is "text/html" — so if you are returning HTML, you don’t necessarily have to set this manually unless you need a different type.


Why This Matters

If you only have one abstract method in a class, you only need to provide the implementation for that method. In the case of GenericServlet, that’s service(). All other lifecycle behavior is already managed by the parent class, allowing you to focus exclusively on how the servlet handles requests and responses.

When extending GenericServlet, you’re no longer burdened with empty method bodies for lifecycle methods you don’t care about. This reduces code length, increases clarity, and speeds up development.


When It’s Loaded

If you don’t configure load-on-startup for a servlet extending GenericServlet, it will not be loaded automatically when the application is deployed. Instead, it will be loaded lazily — only when the first request for it arrives. This behavior is the same as with regular servlets unless explicitly configured otherwise.


Default HTML Behavior

Since the default content type is already "text/html", you can start your response directly with HTML body content. For example, rather than writing full <html>, <head>, and <body> tags, you can simply begin with:

<h1>My Response</h1>

and the browser will still render it correctly as HTML.


This is the point in the chapter where the reader transitions from understanding the servlet lifecycle and request mapping into writing cleaner, more maintainable code. In the next section, this concept is demonstrated with a real example — the FourthServlet implementation using GenericServlet.


9. Example: FourthServlet using GenericServlet

Example: FourthServlet Using GenericServlet

When we first start writing servlets, it’s common to implement the Servlet interface directly. However, as we’ve already discussed earlier in this series, doing so forces us to implement all methods of the interface, even when we only need to write request-processing logic in service(). This quickly becomes repetitive and clutters the code.

This is where GenericServlet comes into play. It’s an abstract adapter class that already implements all methods of the Servlet interface except service(). That means, as a developer, you only have to focus on writing the service() method, significantly reducing boilerplate and improving readability.

By default, the response type (or content type) for GenericServlet is "text/html", which makes it ideal for returning HTML output without having to manually set it every time.


Code Example: FourthServlet

Below is the implementation of a servlet using GenericServlet. This example is decompiled from a .class file using the FernFlower decompiler.

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;

@WebServlet(
   urlPatterns = {"/test"},
   loadOnStartup = 10
)
public class FourthServlet extends GenericServlet {

   public FourthServlet() {
   }

   @Override
   public void service(ServletRequest request, ServletResponse response) 
           throws ServletException, IOException {
      PrintWriter out = response.getWriter();
      out.println("<h1 style='color:blue;'>Writing Servlet using Generic Servlet </h1>");
      out.close();
   }
}

Behind the Scenes

When the above servlet is deployed and accessed via /test, two .class files are involved in the execution:

  1. FourthServlet.class — Your servlet implementation.

  2. GenericServlet.class — The adapter class provided by Java EE.

Here’s what happens step-by-step when a request arrives:

1. Loading

The container first loads FourthServlet.class into memory because it is mapped to the URL pattern /test.

2. Instantiation

The container then creates an object of FourthServlet.

3. Initialization

Initialization in GenericServlet is interesting because it has two init() methods:

  • init(ServletConfig config)

  • init()

The container always calls init(ServletConfig config) first. This method stores the ServletConfig reference in a private variable and then internally calls init().

Why two init methods?

  • init(ServletConfig config) is meant for container-level initialization.

  • init() is meant for developer-written initialization logic.

Best Practice:
If you want to add initialization code in your servlet, override init() instead of init(ServletConfig config). This is because ServletConfig in init(ServletConfig config) is a local variable whose memory is released once the method exits. Overriding init() keeps your code cleaner and avoids interfering with container logic.


Snippet from GenericServlet source code:

public abstract class GenericServlet 
        implements Servlet, ServletConfig, java.io.Serializable {

    private transient ServletConfig config;

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        init(); // Calls developer's no-argument init()
    }

    public void init() throws ServletException {
        // Developer can override this method
    }
}

4. Request Processing

Once initialization is complete, the container calls the service(ServletRequest req, ServletResponse resp) method.

Here’s the order of method resolution:

  • First, it checks if service() is implemented in FourthServlet.

  • If not found, it checks in GenericServlet.

  • Since GenericServlet declares service() as abstract, your servlet must implement it, as shown in our example.

In our code, the service() method writes a blue <h1> heading directly to the response output stream.


5. De-instantiation

When the servlet is removed from service (either because the server is stopped or the application is undeployed), the container calls the destroy() method.

  • It first checks if destroy() is overridden in FourthServlet.

  • If not found, it uses the default implementation from GenericServlet.


Method Resolution Scenarios

Understanding how init() works in different situations is critical for avoiding confusion:

  1. If our servlet class does not contain init() method:

    • GenericServlet: init(ServletConfig config)

    • GenericServlet: init()

    • User Servlet: service(req, resp)

  2. If our servlet class contains init(ServletConfig config) method:

    • User Servlet: init(ServletConfig config)

    • User Servlet: service(req, resp)

  3. If our servlet class contains init() method:

    • GenericServlet: init(ServletConfig config)

    • User Servlet: init()

    • User Servlet: service(req, resp)

Conclusion: For developer logic, init() (no-argument) is the best place to put initialization code.


This section clearly shows why GenericServlet is considered the best example of the Adapter Class Design Pattern—it adapts the Servlet interface into a class that does most of the heavy lifting for you, leaving only the request-processing logic to the developer.

0
Subscribe to my newsletter

Read articles from Rohit Gawande directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rohit Gawande
Rohit Gawande

🚀 Tech Enthusiast | Full Stack Developer | System Design Explorer 💻 Passionate About Building Scalable Solutions and Sharing Knowledge Hi, I’m Rohit Gawande! 👋I am a Full Stack Java Developer with a deep interest in System Design, Data Structures & Algorithms, and building modern web applications. My goal is to empower developers with practical knowledge, best practices, and insights from real-world experiences. What I’m Currently Doing 🔹 Writing an in-depth System Design Series to help developers master complex design concepts.🔹 Sharing insights and projects from my journey in Full Stack Java Development, DSA in Java (Alpha Plus Course), and Full Stack Web Development.🔹 Exploring advanced Java concepts and modern web technologies. What You Can Expect Here ✨ Detailed technical blogs with examples, diagrams, and real-world use cases.✨ Practical guides on Java, System Design, and Full Stack Development.✨ Community-driven discussions to learn and grow together. Let’s Connect! 🌐 GitHub – Explore my projects and contributions.💼 LinkedIn – Connect for opportunities and collaborations.🏆 LeetCode – Check out my problem-solving journey. 💡 "Learning is a journey, not a destination. Let’s grow together!" Feel free to customize or add more based on your preferences! 😊