Java EE Tutorials - Servlet 3.0 Features Part I

Up until this point in our Java EE tutorial series, we’ve talked about absolutely nothing but Servlets. And unless I made a mistake, all of the code examples in prior posts should work using version 2.x or newer of the Servlet specification (and if did make a mistake, please let me know!). I had a good reason for doing this - version 3 of the Servlet spec introduces an alternate, backwards-incompatible method of configuring and deploying Servlets, and while I don’t have numbers indicating the adoption rate of Servlet 3.0 over version 2.x, I do know that enterprises are often slow to upgrade, meaning there’s a good chance that some shops still are (and will continue in) using 2.x Servlets. In that case, it made sense to use examples that are applicable to both environments.

However, now that we’ve covered all the core features, we can safely talk about the new, Servlet 3.0 specific stuff in their own post. All of these features are supported by the demo project we’ve been using. Now, let’s look at some features.

The code for the following examples can be found in the servlet-3 branch of the Github repo we’ve been using. You can find the branch here, and a ZIP file with the branch’s code here.

Feature - Deploying webapps without a web.xml file

The heading says it all really. Servlet 3.0 introduces several annotations which you can apply to your Servlet classes. These annotations allow you to define any of the configuration properties which we previously added to the web.xml file. Here is how a Servlet would be configured:

package net.cmw;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Basic example of a Java Servlet, configured using Servlet 3.0 annotations.
 */
@WebServlet(name = "basic-servlet",
            urlPatterns = {"/basic"},
            description = "Basic Servlet Example",
            initParams = {@WebInitParam(name = "param1", value = "test value")})
public class BasicServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
        ServletException, IOException {

        /*
         * We can add HTTP Header values to the Response
         */
        resp.addHeader("Content-Type", "text/plain");

        /*
         * The response object contains a PrintWriter, which we can use to add content to the response body.
         */
        PrintWriter writer = resp.getWriter();
        writer.print("This is a test string. Init param is " + this.getInitParameter("param1"));
    }
}

The code inside the Servlet is pretty much the same. The big change is the addition of the @WebServlet annotation. Looking inside the annotation, there are several properties, including the Servlet name and description, its URL mapping, and an initialization parameter. These should all be familiar to you if you’ve read any of the web.xml examples we’ve used in the past.

When this Servlet is deployed, the server will scan for any classes containing a Servlet annotation, and will attempt to configure and deploy them automatically. If you run this example, you should still be able to access the Servlet at localhost:8080/basic, even though this code branch has absolutely no web.xml file. In this case, it simply isn’t needed anymore.

Servlets aren’t the only classes configurable via annotation. Here is how to do the same to a Filter:

package net.cmw;

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

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletResponse;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Basic Filter example which logs incoming requests. Uses Servlet 3 annotations.
 */
@WebFilter(filterName = "Basic Filter",
           urlPatterns = {"/*"},
           description = "Basic Filter example using annotations",
           initParams = {@WebInitParam(name = "testValue", value = "test value")})
public class LoggingFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);

    private String testValue;

    /**
     * This method is called when the filter is created. The FilterConfig object passed in contains
     * key/value pairs which can be configured in the web.xml file.
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        testValue = filterConfig.getInitParameter("testValue");
    }

    /**
     * Web apps can have multiple filters, which the server chains together; calling the doFilter() method
     * will pass the request and response objects to the next filter in the chain. When it arrives at the last filter,
     * the doFilter() method will pass the request and response to the Servlet itself.
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {

        logger.info("Request incoming from " + request.getRemoteHost());
        logger.info("Our test value is " + testValue);

        chain.doFilter(request, response);
    }

    /**
     * Called when the filter is being destroyed at web app shutdown. You generally don't have to write anything
     * concrete in this method.
     */
    @Override
    public void destroy() {
    }
}

And once more, for Listeners:

package net.cmw;

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

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

/**
 * Basic example of a Servlet Context Listener.
 */
@WebListener()
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 Servlet Context 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.
    }
}

One thing to note for Listeners: you use the @WebListener annotation for all types of Listeners, be they ServletContextListener, ServletRequestListener, or any of the other Listener types.

web.xml VS annotations

Here’s a question for you - say your webapp is configured with both annotations and a web.xml file. Which configuration takes precedence? It depends.

In Servlet 3.0, there’s a new property you can add to the web.xml, named metadata-complete, which helps dictate precedence. It is used as follows:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation=
             "http://xmlns.jcp.org/xml/ns/javaee
              http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.0"
         metadata-complete="true">
</web-app>

If metadata-complete is set to true, then only the web.xml file will be used to configure the webapp. If set to false (or not set at all), then the server will use both the web.xml file and annotated classes. If a particular Servlet/Filter/etc. is defined via both methods, the XML-based configuration will take precedence.

Keep in mind that scanning for annotation-configured classes can be time consuming, so if you’ve decided on using a web.xml file only, then set this value to “true” to speed up start times.

Why Use This Feature?

Servlet annotations make it easier to spin up a web application. You can write up your classes, annotate them, and get running without having to figure out how to write the web.xml (or worry about typos causing your deployment to crash).

On the other hand, there’s something to be said about configuring all your Servlet code in one place. It allows you to know exactly which classes your webapp is going to deploy. Using annotations, the only way you’ll know which classes contain Servlets/Filters/etc. is if the class and package names are sane and helpful (which should go without saying, but you’d be surprised …).

All things considered, configuring Servlets is now like configuring a Spring Framework application - there’s two ways to do it, and it is up to you to determine which approach is right for your particular scenario.

Feature - Web Fragments

Web Fragments are partial (or complete) web.xml files. You can use them to define as many or as few Servlet components as you’d like. When your application loads, the server will suck up definitions from any Web Fragment files it finds, and add those components to your app.

There are a few rules to using Web Fragments. Firstly, the file needs to be named web-fragment.xml, and it is recommended that you place the file in the META-INF folder of a JAR file.

A Web Fragment file looks just like a web.xml file with a simpler parent tag:

<web-fragment>
    <servlet>
        <servlet-name> Basic Servlet</servlet-name>
        <servlet-class> net.cmw.BasicServlet</servlet-class>
    </servlet>

    <listener>
        <listener-class> net.cmw.BasicListener<listener-class>
    </listener>
</web-fragment>

Why Use This Feature?

Web Fragments are primarily intended to be used by 3rd party libraries and frameworks. For example, say you use a library which provides a Servlet or Filter for you to use (the Spring Framework is a perfect example). Usually the framework’s documentation would tell you how to correctly add and configure this component in your webapp’s ‘web.xml` file. Naturally this is error prone; it is also simply an extra step required to get up and running. If the framework defines these components in a Web Fragment file, then all you have to do is add the framework’s JAR file to the classpath, and those components will automatically be deployed.

You might be asking yourself “Isn’t Web Fragments made redundant by annotation-based configuration?”. The answer is “Not really”. If a 3rd party library configured its Servlet components via annotations, then you would need a decompiler to read the definitions out of the framework’s compiled class files. This is not an issue with an XML file.

Programmatic Servlet Configuration

In Servlet 3.0, we can programatically configure a web application via a ServletContextListener. Here’s an example:

package net.cmw;

import javax.servlet.*;
import javax.servlet.annotation.WebListener;

/**
 * How to programmatically add components to a web application
 */
@WebListener
public class ProgrammaticServletConfigurationListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();

        // We create an instance of ServletRegistration.Dynamic, and add to it
        // all the configuration parameters for the Servlet.
        ServletRegistration.Dynamic dynamic =
                servletContext.addServlet("Dynamic Basic Servlet", "net.cmw.BasicServlet");
        dynamic.addMapping("/basic-dynamic"); // URL mapping

        // Adding Filters and Listeners is done in a similar fashion.
        //
        // FilterRegistration.Dynamic filterDynamic = servletContext.addFilter("new.cmw.LoggingFilter");
        // servletContext.addListener("net.cmw.BasicServletRequestListener");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }
}

Note that this can only be done in a Context Listener, in the contextInitialized() method.

Why Use This Feature?

Programmatic configuration of a Servlet shouldn’t be your first option in most situations. However, it is very handy in cases where you need to perform some sort of complex configuration operations in order to get a component working properly, or if you need to create something conditionally.

Servlet 3.0 Support

Servlet 3.0 has been out long enough that any up-to-date servlet container should have support. For Apache Tomcat you’ll need version 7 or above, and for Jetty you’ll need version 8 or above. I’m not sure about the versions on the big application servers, but the most recent versions of each will support it.

General Observations

Now it is time for some opinion.

All the features described above are helpful, no doubt about it. But it is important to recognize that they’re features of convenience. They offer useful alternatives for web application configuration, which may or may not be useful for any given project. I cannot stress enough how important it is to understand this, as it is in my opinion that Oracle’s marketing folks and tech evangelists like to downplay areas of flexibility within the Java EE stack. For example, I’ve told you that you can use either annotations or XML to configure your application, but I leave it up to you to choose one. That is because I myself don’t have a preference.

However, if this post was written by a good, diehard Java EE hustler, they’d omit any explanation of how to write a web.xml, and make you question your life choices for every considering using it. Don’t fall into this trap. Assess the situation and follow your gut.

Part II?

I meant for this to be a one part article, but about halfway through I realized I needed to break it up. In Part II we will cover Servlet 3.x’s interesting (and fairly complicated) capabilities for asynchronous processing.