/ SERVLET, MAPPING

Tricky servlet mappings!

Last week, I wrote about the creative use one can make of Filters by grading assignments of students. About, there’s another recurring issue that warrants a blog post: servlet mappings. While servlet mappings seem easy on the surface, they sometimes can be the cause of huge headaches.

The issue

In the assignment mentioned above, students have to create a mock e-commerce shop application. The home page shows a title, an introduction phrase, as well as a catchy image. The issue is that the image is not displayed.

At this point, students get creative - this is an assignment after all. Either they discard the requirement to display the image, or they use an image hosted online - hot-linking. The latter amazingly works!

Likewise, JavaScript and CSS files are not found. To make them work, they get inlined.

Let’s see what’s the reason for that.

Analysis

JavaEE webapps offer two path mapping methods:

  1. The servlet mapping one, configured either in the web.xml web deployment descriptor or via @WebServlet annotations on the class.
    @WebServlet("/foo")
    public class FooServlet extends HttpServlet { ... }

    The servlet is now accessible under the /foo path.

  2. The resource one: whatever is not in the WEB-INF folder can be accessed directly.
    sample.war
    |__ image
    |   |__ a.jpg
    |   |__ b.jpg
    |__ script
    |   |__ a.js
    |__ style
    |   |__ a.css
    |___ WEB-INF
        |__ web.xml

    Given the above structure, the following paths are exposed:

    • /image/a.jpg
    • /image/a.jpg
    • /script/a.js
    • /style/a.css

The end user doesn’t know whether a path is served by a servlet or a static resource. A servlet could be mapped to /foo.html. Likewise, one could implement a servlet serving dynamic images and map it to *.jpg.

Now the important bit. When a request is made for a path, the server first looks for matching servlet mappings. If none is found, then it tries to find the relevant static resource by path. Hence, a servlet mapping shadows a static resource if both match the same path.

In the above issue, the culprit is indeed a servlet, and more precisely the one mapped to the / mapping.

@WebServlet("/")
public class HomePageServlet extends HttpServlet { ... }

This is easily demonstrated by running the JVM in debug mode, and setting a breakpoint inside the service() method of the said servlet. It will readily be apparent that it shadows every static resource.

How it works

The Servlet Specifications has a section dedicated to mappings and path matching. Here’s an excerpt:

  1. The container will try to find an exact match of the path of the request to the path of the servlet. A successful match selects the servlet.
  2. The container will recursively try to match the longest path-prefix. This is done by stepping down the path tree a directory at a time, using the / character as a path separator. The longest match determines the servlet selected.
  3. If the last segment in the URL path contains an extension (e.g. .jsp), the servlet container will try to match a servlet that handles requests for the extension. An extension is defined as the part of the last segment after the last . character.
  4. If neither of the previous three rules result in a servlet match, the container will attempt to serve content appropriate for the resource requested. If a "default" servlet is defined for the application, it will be used. Many containers provide an implicit default servlet for serving content.
— Java Servlet Specification

Notice the mention of the default servet. This default servlet is the one mapped to /, and will indeed match every path that is not matched by other servlets, because the longest path matching rules.

Solution

The solution is also found in the specification:

The empty string ("") is a special URL pattern that exactly maps to the application’s context root, i.e., requests of the form http://host:port/<context-root>/. In this case the path info is / and the servlet path and context path is empty string ("").

— Java Servlet Specification

The fix is to just remove the / in the servlet mapped to the home page:

@WebServlet("")
public class HomePageServlet extends HttpServlet { ... }

Conclusion

The devil is in the details. One extra character can wreck one’s application, and send developers into a frenzy of bad workarounds. Most of times, reading the documentation (or specifications) allows to find the correct solution.

To go further:

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Hazelcast. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More