Jakarta Servlet listeners receive lifecycle notifications from the container. Context listeners observe application startup and shutdown, session listeners observe session creation and destruction, and request listeners observe request boundaries. They are useful for initializing and releasing shared infrastructure or collecting lifecycle metrics, not for handling ordinary route logic.
After this lesson, you can select the listener interface that matches an event, keep callbacks thread-safe, avoid slow startup and shutdown work, and understand why some destruction events are not guaranteed after a process crash.
Servlet Listeners are event-driven components that respond to lifecycle events in the web application. They implement specific listener interfaces and are notified when events occur (application startup/shutdown, session creation/destruction, request creation/destruction).
| Listener Interface | Events | Use Case |
|---|---|---|
| ServletContextListener | contextInitialized, contextDestroyed | App startup/shutdown tasks |
| ServletContextAttributeListener | attributeAdded, attributeRemoved, attributeReplaced | Monitor app-scope attributes |
| HttpSessionListener | sessionCreated, sessionDestroyed | Track active sessions |
| HttpSessionAttributeListener | attributeAdded, attributeRemoved, attributeReplaced | Monitor session attributes |
| ServletRequestListener | requestInitialized, requestDestroyed | Request logging, timing |
| HttpSessionBindingListener | valueBound, valueUnbound | Object notified when added to session |
package com.example.listeners;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebListener;
import java.sql.*;
@WebListener
public class AppContextListener implements ServletContextListener {
private Connection dbConnection;
// Called when the web application starts
@Override
public void contextInitialized(ServletContextEvent event) {
ServletContext context = event.getServletContext();
System.out.println("Application starting...");
// Initialize shared resources
try {
// Initialize DB connection pool
Class.forName("com.mysql.cj.jdbc.Driver");
dbConnection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "root", "password");
// Store in application scope for all servlets to use
context.setAttribute("dbConnection", dbConnection);
context.setAttribute("appStartTime", System.currentTimeMillis());
context.setAttribute("appVersion", "1.0.0");
System.out.println("Database connection initialized");
} catch (Exception e) {
System.err.println("Failed to initialize DB: " + e.getMessage());
}
}
// Called when the web application shuts down
@Override
public void contextDestroyed(ServletContextEvent event) {
System.out.println("Application shutting down...");
// Release resources
try {
if (dbConnection != null && !dbConnection.isClosed()) {
dbConnection.close();
System.out.println("Database connection closed");
}
} catch (SQLException e) {
System.err.println("Error closing DB: " + e.getMessage());
}
}
}
@WebListener
public class SessionCountListener implements HttpSessionListener {
// Thread-safe counter for active sessions
private static java.util.concurrent.atomic.AtomicInteger activeSessions
= new java.util.concurrent.atomic.AtomicInteger(0);
@Override
public void sessionCreated(HttpSessionEvent event) {
int count = activeSessions.incrementAndGet();
System.out.println("Session created: " + event.getSession().getId()
+ " | Active sessions: " + count);
// Store count in application scope
event.getSession().getServletContext()
.setAttribute("activeSessions", count);
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
int count = activeSessions.decrementAndGet();
System.out.println("Session destroyed: " + event.getSession().getId()
+ " | Active sessions: " + count);
event.getSession().getServletContext()
.setAttribute("activeSessions", count);
}
public static int getActiveSessions() {
return activeSessions.get();
}
}
@WebListener
public class RequestTimingListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent event) {
// Store start time in request attribute
event.getServletRequest().setAttribute("startTime", System.currentTimeMillis());
}
@Override
public void requestDestroyed(ServletRequestEvent event) {
Long startTime = (Long) event.getServletRequest().getAttribute("startTime");
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
HttpServletRequest req = (HttpServletRequest) event.getServletRequest();
System.out.println("Request to " + req.getRequestURI()
+ " took " + duration + "ms");
}
}
}
ServletContextListener.contextInitialized runs after the web application context is ready and before the application begins normal service. Initialize bounded shared resources here only when the container or dependency-injection framework does not already manage them. Publish a successfully created resource through the ServletContext so consumers have one explicit owner.
contextDestroyed is the matching place to close executors, clients, or pools owned by the listener. Make cleanup idempotent and null-safe because partial startup can leave only some resources initialized. A forced process termination may skip graceful callbacks, so durable correctness must not depend on shutdown code running.
Request listeners are suitable for lightweight measurement or context setup that truly follows the entire servlet request. Filters are usually better when behavior must wrap the chain, alter headers, or stop processing. Session listeners can count active sessions, but a session is not the same as an active human and expiration timing depends on container policy.
Attribute listeners report additions, removals, and replacements. Avoid recursive updates: changing the same attribute from its listener can trigger another event. Treat event payloads as concurrent application data and do not assume callbacks for different requests arrive serially.
Listener callbacks run on container-managed threads. Do not block them with unbounded network calls. Apply timeouts to essential startup checks and fail clearly when the application cannot operate. For nonessential warm-up or telemetry, hand work to a managed executor and define what happens during redeployment.
During local development, redeployment can initialize the application repeatedly. A listener that starts threads or registers static callbacks without releasing them can retain the old classloader and cause memory leaks. Verify that every resource it owns has a matching close path.
Try this next
0 of 2 completed
Explore 500+ free tutorials across 20+ languages and frameworks.