With Spring Boot it's quite easy to set a static context path; it's as simple as setting an environment property. However, what if you wanted multiple context paths? How about a dynamic context path?

In either case it’s quite challenging to accomplish. In my case I had a need for multiple context paths, but I wanted each deployment of the application to expose all of them. After researching the ways I could do this I was unable to find anything online that sufficiently solved my problem.

In lieu of an out-of-box solution, I created a custom servlet filter that customized the incoming request in order to wrap the incoming request. This solution, coincidentally, not only allowed me to have multiple context paths, but also a mechanism to set the context path dynamically.

In this blog I’ll explain the approach I took to implement this functionality along with an explanation of the alternatives.

Weighing the options

To understand the need for a custom solution it’s worth noting the alternatives. In order to have multiple context paths, you're limited to deploying the application multiple times with that convenient property set to what you want for each deployment. However, this is resource expensive because you're spinning up two applications for every one application you would normally spin up.

For dynamic context paths there really is no out-of-box configuration since the context path is static. Now, why would you want multiple or dynamic context paths? This is typically used when you want a prefix in the URL to be effectively ignored by the DispatcherServlet so that you can use the same controller mappings for seemingly very different URLs, and stay within that prefix context after the response.

Take an eCommerce site for example, say you have /food and /equipment URLs that have their own look and feel. When the user browses within one of these context paths, you want to stay within that context when navigating to other links on the page by default however you want to utilize the same Spring application context and controllers for both /food and /equipment context paths on the backend.

Without using a context path for this, your templates would have to be duplicated, or otherwise very complicated, so that URLs don't point you back at the root (i.e. creating a link to search would need to be /food/search not /search so that in your templates you can simply utilize the current context path to stay at /food or without the context path you have to manually set the link to /food/search). For a Thymeleaf application that means being able to simply use @{/search} instead of having to know you’re in /food or /equipment and building the URL accordingly.

Solution

For this example, it's only two context paths, but if you create or remove contexts then, with the out-of-box solution, you now have to spin up more applications for that path or take some down. In order to effectively solve this problem using one application and one servlet, we can use a new Filter and a custom extension of HttpServletRequest. The filter will look at the request URI and determine what the new context path will be, if it needs to be changed at all. Our extension of HttpServletRequest will simply hold our override of the context path and servlet path.

In our Filter we'll determine what the new context path is and then update the context and servlet path accordingly, since both will change in this situation. For example, if we have /food/search and our context path setup through Spring is / then the context path is / and our servlet path is /food/search.

Our Filter will instead use /food as the context path and /search as the servlet path. We'll set that information on our extension of HttpServletRequest and pass our new HttpServletRequest through the rest of the filter chain so that now, when the DispatcherServlet gets our servlet path, it'll get /search and will hit our search controller without us having to modify our controller to look for /{food|equipment}/search.

Extending the HttpServletRequestWrapper

When we look for the context path to build our UI using Thymeleaf, it'll give us /food so that all the links that are generated will have /food prefixed to them. To start with, here's what our extension of HttpServletRequest will look like:

As shown, we're effectively just adding the setContextPath and setServletPath methods since they don't already exist, and then overriding the getters so that we use ours instead of the super's. Next, is our Filter.

Creating the custom Filter

In the above Filter we create an instance of our ServletContextFacadeRequestWrapper, identify if the context path needs to be overridden, update our wrapper accordingly, and then pass it through the filter chain. This can be even more dynamic than shown by using a regex match or always taking the first part of the path as the context path. Lastly, we need to hook up our filter to the filter chain.

Adding the Filter to the FilterChain

With this FilterRegistrationBean, we register our filter very early in the chain so that the context and servlet paths are set correctly very early on.

In summary, we were able to set up an application that acts like it's running on multiple context paths, when it's actually one application with one application context, deployed on a single context path. This was accomplished by wrapping the HttpServletRequest to maintain what we want the context and servlet path to be via a filter in the filter chain.