The Servlet tl-container manages the complete lifecycle of a Servlet. The lifecycle consists of three main phases:
init() is called once when the Servlet is first loadedservice() is called for every client requestdestroy() is called once when the Servlet is removed from serviceThe tl-container creates only one instance of each Servlet and handles concurrent requests using multiple threads. This is why Servlet instance variables must be thread-safe.
package com.example;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.WebServlet;
import java.io.*;
import java.util.concurrent.atomic.AtomicInteger;
@WebServlet("/lifecycle")
public class LifecycleServlet extends HttpServlet {
// Use AtomicInteger for thread-safe counter
private AtomicInteger requestCount = new AtomicInteger(0);
private String initMessage;
// ===== PHASE 1: INITIALIZATION =====
// Called ONCE when servlet is first loaded (or at startup if load-on-startup is set)
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
// Read init parameters
initMessage = config.getInitParameter("message");
if (initMessage == null) initMessage = "Default message";
System.out.println("[INIT] LifecycleServlet initialized. Message: " + initMessage);
}
// ===== PHASE 2: REQUEST HANDLING =====
// service() dispatches to doGet/doPost/etc. based on HTTP method
// You can override service() directly, but it's better to override doGet/doPost
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("[SERVICE] Handling request #" + requestCount.incrementAndGet());
super.service(req, resp); // Delegates to doGet/doPost/etc.
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("<h2>Servlet Lifecycle Demo</h2>");
out.println("<p>Init message: " + initMessage + "</p>");
out.println("<p>Total requests: " + requestCount.get() + "</p>");
out.println("<p>Thread: " + Thread.currentThread().getName() + "</p>");
}
// ===== PHASE 3: DESTRUCTION =====
// Called ONCE when servlet is removed (server shutdown or undeploy)
@Override
public void destroy() {
System.out.println("[DESTROY] LifecycleServlet destroyed. Total requests served: "
+ requestCount.get());
// Release resources: close DB connections, stop threads, etc.
}
}
By default, Servlets are loaded lazily - on the first request. You can force eager loading at startup using load-on-startup:
// loadOnStartup = 1 means load at startup, priority 1 (loads before priority 2, 3, etc.)
@WebServlet(urlPatterns = "/app", loadOnStartup = 1)
public class AppInitServlet extends HttpServlet {
@Override
public void init() throws ServletException {
// This runs at server startup
System.out.println("Application initialized at startup");
// Initialize shared resources: DB connection pool, caches, etc.
getServletContext().setAttribute("appStartTime", System.currentTimeMillis());
}
}
<servlet>
<servlet-name>AppInitServlet</servlet-name>
<servlet-class>com.example.AppInitServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>DataServlet</servlet-name>
<servlet-class>com.example.DataServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
Explore 500+ free tutorials across 20+ languages and frameworks.