Servlets · Lesson 2
Servlet Lifecycle
Understanding the Servlet Lifecycle
The servlet lifecycle describes the key phases a servlet goes through from creation to destruction. A servlet container manages this lifecycle: initialization (happens once), request handling (happens many times), and destruction (happens once at shutdown). Understanding this lifecycle helps you implement initialization logic efficiently and ensure proper resource cleanup.
Initialization: The init() Method
When the servlet container starts or receives the first request for a servlet, it calls the init() method once. Use init() to perform one-time setup: loading configuration files, initializing database connections, preparing resources that will be reused across many requests. This is much more efficient than initializing per-request.
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
public class DatabaseServlet extends HttpServlet {
private Connection dbConnection;
@Override
public void init() throws ServletException {
// Called once when servlet is created
try {
// Initialize database connection
Class.forName("com.mysql.jdbc.Driver");
dbConnection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "root", "password"
);
System.out.println("Database connection initialized");
} catch (Exception e) {
throw new ServletException("Failed to initialize database", e);
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Use the initialized connection
if (dbConnection != null) {
// Query database using connection
response.getWriter().println("Database connected!");
}
}
}
Request Handling: Service Request and Response
After initialization, the servlet enters the request-response phase. For each HTTP request, the container calls a method like doGet(), doPost(), doPut(), or doDelete(). Each request is typically handled by a separate thread, so the servlet must be thread-safe if it accesses shared resources. This phase continues until the servlet is shut down.
public class CounterServlet extends HttpServlet {
private int requestCount = 0; // Shared state
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Problem: requestCount is not thread-safe!
// Multiple threads can read/modify simultaneously
requestCount++;
response.setContentType("text/plain");
response.getWriter().println("Total requests: " + requestCount);
}
}
// Better: Thread-safe approach
public class ThreadSafeCounterServlet extends HttpServlet {
private volatile int requestCount = 0; // volatile helps, but sync is better
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
synchronized (this) { // Ensure thread safety
requestCount++;
}
response.setContentType("text/plain");
response.getWriter().println("Total requests: " + requestCount);
}
}
Destruction: The destroy() Method
When the servlet container shuts down (or the application is undeployed), it calls the destroy() method once. Use destroy() to clean up resources: close database connections, release file handles, stop background threads. Proper cleanup prevents resource leaks.
public class ResourcefulServlet extends HttpServlet {
private Connection dbConnection;
private FileWriter logFile;
@Override
public void init() throws ServletException {
try {
// Initialize resources
dbConnection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "pass");
logFile = new FileWriter("servlet.log", true);
logFile.write("Servlet initialized\n");
} catch (Exception e) {
throw new ServletException(e);
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
// Use resources
logFile.write("Request received\n");
logFile.flush();
} catch (IOException e) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
@Override
public void destroy() {
// Clean up resources
try {
if (dbConnection != null && !dbConnection.isClosed()) {
dbConnection.close();
System.out.println("Database connection closed");
}
if (logFile != null) {
logFile.write("Servlet destroyed\n");
logFile.close();
System.out.println("Log file closed");
}
} catch (Exception e) {
System.err.println("Error during destroy: " + e.getMessage());
}
}
}
Servlet Container and Context
The servlet container (Tomcat, Jetty) manages multiple servlets. The ServletContext represents the entire web application and provides access to shared resources like application attributes, init parameters, and the file system. You can access it via getServletContext().
public class ContextServlet extends HttpServlet {
@Override
public void init() throws ServletException {
ServletContext context = getServletContext();
// Get init parameters (from web.xml or annotations)
String appName = context.getInitParameter("appName");
String version = context.getInitParameter("version");
// Store application-wide data
context.setAttribute("applicationName", appName);
context.setAttribute("startTime", System.currentTimeMillis());
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletContext context = getServletContext();
// Retrieve application-wide data
String appName = (String) context.getAttribute("applicationName");
Long startTime = (Long) context.getAttribute("startTime");
response.setContentType("text/plain");
response.getWriter().println("App: " + appName);
response.getWriter().println("Uptime: " + (System.currentTimeMillis() - startTime) + " ms");
}
}
| Lifecycle Phase | Method | Called | Purpose |
|---|---|---|---|
| Initialization | init() | Once, at startup | Set up resources (DB, files) |
| Request Handling | doGet(), doPost(), etc. | Many times | Process HTTP requests |
| Destruction | destroy() | Once, at shutdown | Clean up resources |
Common Mistake: Not making servlets thread-safe. Each request is handled by a different thread, so shared fields can cause race conditions. Either make fields immutable, use
synchronized, or use request-scoped objects (stored in request attributes). Never assume only one request runs at a time.
Best Practices for Servlet Lifecycle
- Initialize expensive resources in
init(), not in request handlers - Always clean up in
destroy(): close connections, files, streams - Use
volatileorsynchronizedfor shared mutable state - Prefer immutable objects: they're inherently thread-safe
- Use ServletContext for application-wide data shared across servlets
- Handle exceptions in init() and propagate as ServletException
- Test shutdown scenarios: verify resources are properly released
- Avoid long-running operations in request handlers: offload to background tasks
Real-World Example: Database-Backed Web Application
A servlet-based application initializes a database connection pool in init() once at startup. Each incoming request is handled by a separate thread that borrows a connection from the pool (thread-safe), executes a query, and returns the connection. The destroy() method closes all connections in the pool when the application shuts down. By managing the lifecycle carefully, the application efficiently serves hundreds of concurrent users without leaking resources or crashing on shutdown.
Quiz Check