Java EE Tutorials - Servlet Listeners

Our tour of the Java Servlet Specification is almost finished. So far we have covered Servlets, Filters, and the Servlet Context. Now we will look the last major Servlet feature - Listeners.

If you’ve spent enough time writing Java, chances are you have encountered the listener concept before. They wait for some event to occur, at which point they will trigger and perform some action. In general, listeners can be made to listen for any sort of event, but if we want to use the Servlet-specific listener classes provided by the API, we are limited to only a predetermined set of events. When you consider that the purpose of a Servlet is to simply process requests and responses, these events are really all you need.

The Servlet API provides several types of Listeners in the form of Java Interfaces. We aren’t going to cover all of the Listeners in this post, instead focusing on the most commonly used ones. The first is ServletContextListener, which listens for two events - when the Servlet Context is created, and when it is destroyed. The next interface is ServletRequestListener, which also listens to two events - when an incoming request is created, and when it is destroyed.

Our code example for this post can be found in the servlet_listeners branch of my Java Servlet Github repository. If you aren’t using Git, you can download the code as a ZIP file here.

Listeners are configured and implemented in much the same way as Filters. Let’s start with a simple ServletContextListener:

package net.cmw;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 * Basic example of a Servlet Context Listener.
 */
public class BasicServletContextListener implements ServletContextListener {
    private Logger logger = LoggerFactory.getLogger(BasicServletContextListener.class);

    /**
     * Called when the Servlet Context is initialized
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // Let's annoucne that the context is ready
        logger.info("Context is initialized");

        // We have access to the ServletContext in here
        ServletContext context = sce.getServletContext();

        // This method is also a good place to run other tasks, such as:
        // * Initializing Spring beans
        // * Starting other, non-Servlet listeners
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        logger.info("Context is destroyed");

        // This method is also a good place to run other tasks, such as:
        // * Destroying Spring beans
        // * Stopping other, non-Servlet listeners
        // * Closing stray threads that are created (but not cleaned up)
        //   by other objects.
    }
}

Since we want to keep the example simple, we aren’t doing anything beyond logging the events. You should read the comments carefully, however, as they describe some of the common actions performed in a ServletContextListener. Also notice that we are able to access the ServletContext object from either method (though I only demosntrated this in the contextInitialized method.

Now we will write a ServletRequestListener, which is no more or less difficult:

package net.cmw;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;

/**
 * Basic example of a Servlet Request Listener
 */
public class BasicServletRequestListener implements ServletRequestListener {
    Logger logger = LoggerFactory.getLogger(BasicServletRequestListener.class);

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        logger.info("Request incoming!");

        // We can access both the Context and the Request
        ServletContext context = sre.getServletContext();
        ServletRequest request = sre.getServletRequest();
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        logger.info("Request finished");
    }
}

This time, we have access to the incoming request object, in addition to the global context object.

We now have to configure these two listeners in our web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app
        version="3.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

    <display-name>Basic Servlet Example</display-name>

    <listener>
        <listener-class> net.cmw.BasicServletContextListener</listener-class>
    </listener>

    <listener>
        <listener-class> net.cmw.BasicServletRequestListener</listener-class>
    </listener>

    <servlet>
        <servlet-name> Example Servlet</servlet-name>
        <servlet-class> net.cmw.BasicServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name> Example Servlet</servlet-name>
        <url-pattern> /basic</url-pattern>
    </servlet-mapping>

</web-app>

All we have to do is declare the Listeners. We are unable to pass them special configuration options, like we can for Servlets and Filters. If you need to pass along some config. property, you will have to add it to the ServletContext object, and have your Listener classes grab it from there.

If we run the example, the Servlet will act like normal, but we’ll see the following output in the logs:

16:21:42.158 [main] INFO  net.cmw.BasicServletContextListener - Context is initialized
16:21:42-05:00
16:21:42-05:00
16:21:42-05:00
Press any key to stop the server.
16:21:48.311 [qtp288379405-18] INFO  net.cmw.BasicServletRequestListener - Request incoming!
16:21:48.341 [qtp288379405-18] INFO  net.cmw.BasicServletRequestListener - Request finished
16:21:48.349 [qtp288379405-15] INFO  net.cmw.BasicServletRequestListener - Request incoming!
16:21:48.362 [qtp288379405-15] INFO  net.cmw.BasicServletRequestListener - Request finished
16:21:48.370 [qtp288379405-19] INFO  net.cmw.BasicServletRequestListener - Request incoming!
16:21:48.372 [qtp288379405-19] INFO  net.cmw.BasicServletRequestListener - Request finished
16:21:53.485 [qtp288379405-18] INFO  net.cmw.BasicServletRequestListener - Request incoming!
16:21:53.488 [qtp288379405-18] INFO  net.cmw.BasicServletRequestListener - Request finished

The very first line of output shows our BasicServletContextListener being triggered. This is followed later on by several invocations of our BasicServletRequestListener. Since I shut the server down forcefully, there was no output when the Servlet Context was destroyed; hopefully you get the gist.

General Observations

You might be asking yourself what the difference is between a Servlet Filter and a Request Listener. After all, they both have access to incoming requests. Why use one over the other?

In reality, there are quite a few difference between Filters and Listeners, some more obvious than others:

  • Listeners do not have access to Response objects, so they can’t be used for post-processing.
  • Listeners cannot be mapped to specific URL’s. They are all or nothing.
  • When a Request is passed through a Filter Chain, each Filter processes it one at a time. Listeners, on the other hand, are not part of this chain, and so the same guarnatee cannot be made. This email does a fine job explaining this in more technical terms. In case that link ever dies, I’ll quote the email here:

While you can modify the current event object within a listener, you cannot halt the execution of the current event handler in a listener. You also cannot clear the event queue from within a listener. Besides the imposed differences in capabilities, they are also intended for different purposes. Listeners tend to focus on interacton between the event handler and the model, while filters tend to focus on interaction between the event handler and the controller.

  • The Listeners I demonstrated are the two which I find to be the most commonly used. However, there are others. The ServletRequestAttributeListener and ServletContextAttributeListener can be triggered when properties are added or removed from a Request or the Servlet Context respectively. There is also the AsyncListener, which is specific to version 3.0 of the Servlet API, which has support for asynchronous requests.

TL;DR - If you need to modify a Request, use a Filter. If you need to execute something unrelated to the Request when the Request arrives, use a Listener.

And with that, we have covered all of the core features of the Servlet specification. But we aren’t quite done yet. In another post, I’d like to go over some features which are new and exlusive to Servlet version 3.0, the latest (and current) version of the spec.