Without doubt one of the most important Servlet implementations is done by Tomcat. Tomcat serves, or has served, as the base for Servlet functionality in a number of Java EE application servers and is one of the most frequently used standalone Servlet containers.

Contrary to what is often thought, Tomcat is not the reference implementation for Servlet; the build-in Servlet container of GlassFish is. Even though important parts of that GlassFish Servlet implementation are in fact based on Tomcat it's a different code base. Normally GlassFish being the RI implements new spec level functionality first, but unfortunately during the Java EE 8/Servlet 4.0 cycle the GlassFish team is working on other assignments and hasn't worked out a schedule to incorporate this new functionality.

Luckily Tomcat has taken the initiative and in Tomcat 9.0.0.M4 the proposed Servlet 4.0 Mapping API has been implemented.

This API allows Servlet users to find out via which mapping a Servlet was being called. E.g. a Servlet can be mapped by a user to both "/foo/*" and "*.bar" among others. Especially for frameworks it can be important to know what the mapping was, since not rarely something (typically a template file) has to be loaded based on what the * from the above example was. And not only that, if links have to be generated they often need to use the same mapping that was used to call the Servlet.

The current way to do this is a little hairy and therefor quite error prone; it requires checking against the many components of the request URI. The new proposed API greatly simplifies this via the new HttpServletRequest#getMapping() method:

public default Mapping getMapping() { // ... }

/** * Represents how the request from which this object was obtained was mapped to * the associated servlet. * * @since 4.0 */ public interface Mapping { /** * @return The value that was matched or the empty String if not known. */ String getMatchValue(); /** * @return The {@code url-pattern} that matched this request or the empty * String if not known. */ String getPattern(); /** * @return The type of match ({@link MappingMatch#UNKNOWN} if not known) */ MappingMatch getMatchType(); }

/** * Represents the ways that a request can be mapped to a servlet * * @since 4.0 */ public enum MappingMatch { CONTEXT_ROOT, DEFAULT, EXACT, EXTENSION, IMPLICIT, PATH, UNKNOWN }

@WebServlet({"/path/*", "*.ext", "", "/", "/exact"}) public class Servlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Mapping mapping = request.getMapping(); response.getWriter() .append("Mapping match:") .append(mapping.getMatchType().name()) .append("

") .append("Match value:") .append(mapping.getMatchValue()) .append("

") .append("Pattern:") .append(mapping.getPattern()); } }

The newtype that can be seen in the signature of the above method looks as follows:Theis an enumeration of the different types of possible mappings as shown below:To test out the new functionality the following test Servlet was used:

As can be seen this Servlet is mapped via a path mapping ("/path/*"), extension mapping ("*.ext"), context root mapping (""), default mapping ("/") and a single exact mapping ("/exact"). We deployed this Servlet via an application called "servlet4" to a Tomcat 9.0 M4 instance and subjected it to a couple of requests via a browser. The results are shown below:

Path mapping

http://localhost:8080/servlet4/path/foo

Mapping match:PATH Match value:/foo Pattern:/path/*

Extension mapping

http://localhost:8080/servlet4/foo.ext

Mapping match:EXTENSION Match value:/foo Pattern:*.ext

Context root mapping

http://localhost:8080/servlet4

Mapping match:CONTEXT_ROOT Match value: Pattern:

Default (fallback) mapping

http://localhost:8080/servlet4/doesnotexist

Mapping match:DEFAULT Match value:/ Pattern:/

Exact mapping

http://localhost:8080/servlet4/exact

Mapping match:EXACT Match value:/exact Pattern:/exact

<% Mapping mapping = request.getMapping(); response.getWriter() .append("Mapping match:") .append(mapping.getMatchType().name()) .append("

") .append("Match value:") .append(mapping.getMatchValue()) .append("

") .append("Pattern:") .append(mapping.getPattern()); %>

To test the implicit mapping (as per Servlet spec 12.2.1), the following JSP file was used:This was however seen by Tomcat as a regular extension mapping:

Implicit mapping

http://localhost:8080/servlet4/page.jsp

Mapping match:EXTENSION Match value:/page Pattern:*.jsp

Implicit mappings are a little bit difficult to represent. Are they a mapping themselves, or is it just extra information? I.e. can you speak of an "implicit extension mapping" and an "implicit path mapping"?

Conclusion

At the moment it's unfortunately difficult to commit code to the Servlet RI (GlassFish), but Tomcat 9.0 M4 can be used to get an early glimpse of one of the new Servlet APIs. As the examples have shown, the new Mapping API now makes it trivial to find out via which mapping a Servlet was selected. The "implicit" mapping type however may still need some discussion.

Arjan Tijms