'Multiple servlet mappings in Spring Boot

Is there any way to set via property 'context-path' many mappings for a same Spring Boot MVC application? My goal is to avoid creating many 'Dispatcherservlet' for the uri mapping.

For example:

servlet.context-path =/, /context1, context2


Solution 1:[1]

You only need a single Dispatcherservlet with a root context path set to what you want (could be / or mySuperApp).

By declaring multiple @RequestMaping, you will be able to serve different URI with the same DispatcherServlet.

Here is an example. Setting the DispatcherServlet to /mySuperApp with @RequestMapping("/service1") and @RequestMapping("/service2") would exposed the following endpoints :

/mySuperApp/service1

/mySuperApp/service2

Having multiple context for a single servlet is not part of the Servlet specification. A single servlet cannot serve from multiple context.

What you can do is map multiple values to your requesting mappings.

@RequestMapping({"/context1/service1}", {"/context2/service1}")

I don't see any other way around it.

Solution 2:[2]

You can create @Bean annotated method which returns ServletRegistrationBean , and add multiple mappings there. This is more preferable way, as Spring Boot encourage Java configuration rather than config files:

@Bean
    public ServletRegistrationBean myServletRegistration()
    {
        String urlMapping1 = "/mySuperApp/service1/*";
        String urlMapping2 = "/mySuperApp/service2/*";
        ServletRegistrationBean registration = new ServletRegistrationBean(new MyBeautifulServlet(), urlMapping1, urlMapping2);

        //registration.set... other properties may be here
        return registration;
    }

On application startup you'll be able to see in logs:

INFO  | localhost | org.springframework.boot.web.servlet.ServletRegistrationBean | Mapping servlet: 'MyBeautifulServlet' to [/mySuperApp/service1/*, /mySuperApp/service2/*]

Solution 3:[3]

You can use 'server.contextPath' property placeholder to set context path for the entire spring boot application. (e.g. server.contextPath=/live/path1)

Also, you can set class level context path that will be applied to all the methods e.g.:

@RestController
@RequestMapping(value = "/testResource", produces = MediaType.APPLICATION_JSON_VALUE)
public class TestResource{

@RequestMapping(method = RequestMethod.POST, value="/test", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<TestDto> save(@RequestBody TestDto testDto) {
...

With this structure, you can use /live/path1/testResource/test to execute save method.

Solution 4:[4]

None of the answers to this sort of question seem to mention that you'd normally solve this problem by configuring a reverse proxy in front of the application (eg nginx/apache httpd) to rewrite the request.

However if you must do it in the application then this method works (with Spring Boot 2.6.2 at least) : https://www.broadleafcommerce.com/blog/configuring-a-dynamic-context-path-in-spring-boot.

It describes creating a filter, putting it early in the filter chain and basically re-writing the URL (like a reverse proxy might) so that requests all go to the same place (ie the actual servlet.context-path).

Solution 5:[5]

I've found an alternative to using a filter described in https://www.broadleafcommerce.com/blog/configuring-a-dynamic-context-path-in-spring-boot that requires less code.

This uses RewriteValve (https://tomcat.apache.org/tomcat-9.0-doc/rewrite.html) to rewrite urls outside of the context path e.g. if the real context path is "context1" then it will map /context2/* to /context1/*

@Component
public class LegacyUrlWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    private static final List<String> LEGACY_PATHS = List.of("context2", "context3");

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        RewriteValve rewrite = new RewriteValve() {
            @Override
            protected void initInternal() throws LifecycleException {
                super.initInternal();
                try {
                    String config = LEGACY_PATHS.stream() //
                            .map(p -> String.format("RewriteRule ^/%s(/.*)$ %s$1", p, factory.getContextPath())) //
                            .collect(Collectors.joining("\n"));
                    setConfiguration(config);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };
        factory.addEngineValves(rewrite);
    }
}

If you need to use HTTP redirects instead then there is a little bit more required (to avoid a NullPointerException in sendRedirect):

@Component
public class LegacyUrlWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    private static final List<String> LEGACY_PATHS = List.of("context2", "context3");

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        RewriteValve rewrite = new RewriteValve() {
            @Override
            protected void initInternal() throws LifecycleException {
                super.initInternal();
                try {
                    String config = LEGACY_PATHS.stream() //
                            .map(p -> String.format("RewriteRule ^/%s(/.*)$ %s$1 R=permanent", p, factory.getContextPath())) //
                            .collect(Collectors.joining("\n"));
                    setConfiguration(config);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public void invoke(Request request, Response response) throws IOException, ServletException {
                if (request.getContext() == null) {
                    String[] s = request.getRequestURI().split("/");
                    if (s.length > 1 && LEGACY_PATHS.contains(s[1])) {
                        request.getMappingData().context = new FailedContext();
                    }
                }
                super.invoke(request, response);
            }

        };
        factory.addEngineValves(rewrite);
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Nicholas K
Solution 2 stinger
Solution 3 Darshan Mehta
Solution 4 BenHT
Solution 5 Damon Horrell