Web Listeners
Web Listeners
Servlet filters intercept individual requests. But many cross-cutting concerns — startup initialisation, connection-pool warm-up, metrics counters, audit trails — have nothing to do with any single request. They care about lifecycle events: the application started, a session was created, a request attribute was removed. That is exactly what web listeners are for.
The Servlet specification (Jakarta EE 10, package jakarta.servlet) defines eight listener interfaces organised into three scopes: context (the whole application), session, and request. You implement the interface, annotate the class with @WebListener, and the container calls your methods at the right moments — no wiring, no XML (unless you prefer it).
Context Listeners — Application Scope
ServletContextListener is the most important listener you will write. Its two methods bracket the entire lifetime of the web application:
contextInitialized(ServletContextEvent)— called once after the container finishes loading the app but before any request is processed. Use it for one-time startup work.contextDestroyed(ServletContextEvent)— called once when the container is shutting the app down (undeploy, server restart). Use it to release resources gracefully.
Any servlet can now retrieve the pool with (DataSource) request.getServletContext().getAttribute("dataSource") — no static singletons, no dependency-injection framework required for this pattern.
ServletContext ties its lifecycle to the application rather than the JVM. When the app is redeployed without restarting the server, contextDestroyed runs first, closing the pool cleanly. A static field would outlive the context and leak connections to the old class loader.
ServletContextAttributeListener is the companion interface: it fires attributeAdded, attributeReplaced, and attributeRemoved whenever anyone calls setAttribute / removeAttribute on the context. It is useful for auditing or reacting to configuration changes at runtime.
Session Listeners — Session Scope
HttpSessionListener tracks session birth and death across all users:
sessionCreated(HttpSessionEvent)— fired whenrequest.getSession(true)creates a brand-new session.sessionDestroyed(HttpSessionEvent)— fired when a session is invalidated (session.invalidate()) or times out.
sessionDestroyed when the session timed out. By the time the container calls sessionDestroyed for a timed-out session, the session attributes may already have been cleared. Read the attributes you need in sessionCreated (or store them elsewhere first) if you want to use them at destruction time. When session.invalidate() is called explicitly the attributes are still present at the point of the call.
HttpSessionAttributeListener fires on attribute changes within any session — useful for tracking when a user object is placed into or removed from a session (login/logout audit trail).
HttpSessionBindingListener is different: it is implemented on the value object itself (e.g., your User class), not on a separate listener class. When an instance implementing this interface is set as a session attribute, the container calls valueBound on it; when it is removed, valueUnbound is called. This lets the object manage its own cleanup without a global listener scanning every session.
HttpSessionActivationListener supports passivation (serialising sessions to disk or another node). sessionWillPassivate is called before serialisation; sessionDidActivate after deserialisation. Implement this on any session attribute that holds a non-serialisable resource (e.g., a live socket) so it can release and re-acquire it correctly.
Request Listeners — Request Scope
ServletRequestListener is the request-scoped equivalent of ServletContextListener. Its methods surround every single HTTP request that enters the application:
Unlike a filter, this listener does not sit in the processing chain, so it cannot block or modify the request. Its role is purely observational — telemetry, per-request MDC (Mapped Diagnostic Context) setup for logging, and similar cross-cutting concerns that should not affect the response.
Choosing the Right Listener
- Startup / shutdown work (pool init, cache pre-warm, scheduled job start/stop) →
ServletContextListener - Counting active users, enforcing concurrent-login limits →
HttpSessionListener - Audit trail for login/logout →
HttpSessionAttributeListenerorHttpSessionBindingListener - Passivating clustered sessions →
HttpSessionActivationListener - Per-request telemetry, MDC setup →
ServletRequestListener
Listener Ordering and Thread Safety
Multiple listeners of the same type are called in the order the container discovers them (annotation-based discovery order is unspecified; use web.xml <listener> entries if ordering matters). contextInitialized and contextDestroyed are called on a single thread; session and request listeners may be called concurrently across many threads. Any mutable state shared between listener invocations must be thread-safe — use AtomicInteger, ConcurrentHashMap, or synchronized blocks accordingly.
@WebListener — register listeners as Spring beans using @Bean methods that return EventListener registrations, or implement the relevant interface and annotate with @Component. Spring wraps the Servlet container events in its own application-event system, so you can also listen for ApplicationReadyEvent or ContextClosedEvent instead of the raw Servlet listener interfaces. But when you work with plain Jakarta EE containers (Tomcat, Jetty, WildFly without Spring), the raw listener interfaces are exactly what you reach for.
Summary
Web listeners give you lifecycle hooks at three scopes. Use ServletContextListener to initialise and tear down application-wide resources at startup and shutdown — connection pools, caches, background threads. Use HttpSessionListener to count sessions, enforce limits, or keep an audit log of logins and logouts. Use ServletRequestListener for observability: timing, per-request logging context, and metrics that should not alter the response. The right listener used in the right scope is far cleaner than scattering the same logic across every servlet or filter.