While preparing an application to use the upcoming Spring Boot v3 a lot of tests fail where MockMvc is used. The application is configured with a server.servlet.context-path which is also used in the tests. For Spring Boot v2 these tests are successfully running. The minimal test is:

@SpringBootTest
@AutoConfigureMockMvc
class DemoApplicationTests {

    @Autowired
    private MockMvc mockMvc;

    @Value("${server.servlet.context-path}")
    private String contextPath;

    @Test
    public void shouldServeIndex_mockMvc() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get(contextPath + "/").contextPath(contextPath))
                .andExpect(status().isOk());
    }

    @Test
    public void shouldServeIndex_webClient() throws Exception {
        try (final var webClient = MockMvcWebClientBuilder.mockMvcSetup(mockMvc).contextPath(contextPath).build()) {
            webClient.getPage("http://localhost" + contextPath + "/");
        }
    }

}

Running with Spring Boot 3.0.0-RC2 both tests fail with:

[ERROR] shouldServeIndex_mockMvc  Time elapsed: 0.236 s  <<< ERROR!
jakarta.servlet.ServletException: Request processing failed: java.lang.IllegalArgumentException: Cannot build an application for a request which servlet context does not match with the application that it is being built for.
        at com.example.demo.DemoApplicationTests.shouldServeIndex_mockMvc(DemoApplicationTests.java:26)
Caused by: java.lang.IllegalArgumentException: Cannot build an application for a request which servlet context does not match with the application that it is being built for.
        at com.example.demo.DemoApplicationTests.shouldServeIndex_mockMvc(DemoApplicationTests.java:26)

The check in org.thymeleaf.web.servlet.JakartaServletWebApplication#servletContextMatches fails because the provided current context path is not equal (it's empty) to the requested one. The module for Spring (Framework) 5 does not check the context paths.

A test project can be found here.

Comment From: wilkinsona

Thanks for the report, @agebhar1. Unfortunately, it has left me a little bit confused. Thanks to your analysis, we know that the change in behavior is due to a change in Thymeleaf yet you have opened a Spring Boot issue rather than a Thymeleaf issue. Can you please explain why you did that? Do you consider Thymeleaf's new check to be correct and expect a change in Spring Boot/MockMvc so that it passes?

Comment From: philwebb

Although the check is new in Thymeleaf, I think the problem might actually be on our side. The SpringBootMockServletContext that we attach from SpringBootContextLoader doesn't call setContextPath. The mock MVC call in the test does set it so there's a mismatch between servletContext.getContextPath() and httpServletRequest.getContextPath().

Comment From: agebhar1

Hello @wilkinsona, thank's for your attention. Finally, I expect the tests to be passed. I assume that Thymeleaf's check is correct and the context path is not propagated correctly. As far as I can see is that the provided ServletContext to the ThymeleafView(which is an extension of WebApplicationObjectSupport) contains an object of class SpringBootMockServletContext. The object has an empty value ("") for the attribute contextPath. Whereby the configuration for server.servlet.context-path is set.

Comment From: philwebb

I'm a little worried that if we try and set the context path in the tests then it might break a lot of users in the other direction.

The javadoc for MockHttpServletRequestBuilder.contextPath(...) states:

In most cases, tests can be written by omitting the context path from the requestURI. This is because most applications don't actually depend on the name under which they're deployed. If specified here, the context path must start with a "/" and must not end with a "/".

If we set the context path on our mock servlet then all mock MVC calls will need to remember to also set it.

If I drop it entirely from the tests, things work:

@SpringBootTest
@AutoConfigureMockMvc
class DemoApplicationTests {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldServeIndex_mockMvc() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/")).andExpect(status().isOk());
    }

    @Test
    public void shouldServeIndex_webClient() throws Exception {
        try (final var webClient = MockMvcWebClientBuilder.mockMvcSetup(mockMvc).build()) {
            webClient.getPage("http://localhost/");
        }
    }

}

Comment From: wilkinsona

Thymeleaf 3.1's JavaxServletWebApplication has the same check in it so this will also fail with Spring Boot 2.x if someone uses Thymeleaf 3.1.

Comment From: philwebb

@agebhar1 We're discussing our options for this issue. Did dropping the context path entirely work for you? If not, the best idea we've had so far is to add some kind of annotation to the test that signals we should create SpringBootMockServletContext with the context path set.

Comment From: agebhar1

@philwebb thank you for discussing this issue. It will work for me to remove the context path from the Tomcat configuration and add them to all controller. I'd like the test URLs to be equal to the productive ones. Maybe a hint should be added to the documentation to be aware of the situation.

Comment From: tkrah

Little bit of a hookup but I get the same exception with spring boot 3 and Thymeleaf 3.1 and using a ForwardedHeaderFilter (X-Forwarded-Prefix is /XXXX/myapp) - the context-path used to deploy the app is just "/myapp" does differ from the mounted forward path and generated the same error - should I file a new bug for that - by whom, thymyleaf or spring-boot?

java.lang.IllegalArgumentException: Cannot build an application for a request which servlet context does not match with the application that it is being built for.
    at org.thymeleaf.util.Validate.isTrue(Validate.java:79) ~[thymeleaf-3.1.0.RELEASE.jar!/:3.1.0.RELEASE]
    at org.thymeleaf.web.servlet.JakartaServletWebApplication.buildExchange(JakartaServletWebApplication.java:64) ~[thymeleaf-3.1.0.RELEASE.jar!/:3.1.0.RELEASE]
    at org.thymeleaf.spring6.view.ThymeleafView.renderFragment(ThymeleafView.java:204) ~[thymeleaf-spring6-3.1.0.RELEASE.jar!/:3.1.0.RELEASE]
    at org.thymeleaf.spring6.view.ThymeleafView.render(ThymeleafView.java:192) ~[thymeleaf-spring6-3.1.0.RELEASE.jar!/:3.1.0.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1414) ~[spring-webmvc-6.0.2.jar!/:6.0.2]
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1158) ~[spring-webmvc-6.0.2.jar!/:6.0.2]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1097) ~[spring-webmvc-6.0.2.jar!/:6.0.2]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973) ~[spring-webmvc-6.0.2.jar!/:6.0.2]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003) ~[spring-webmvc-6.0.2.jar!/:6.0.2]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:895) ~[spring-webmvc-6.0.2.jar!/:6.0.2]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:705) ~[tomcat-embed-core-10.1.1.jar!/:6.0.0]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880) ~[spring-webmvc-6.0.2.jar!/:6.0.2]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814) ~[tomcat-embed-core-10.1.1.jar!/:6.0.0]

The servlet context-path is here always different between the deployed app and the HttpServletRequest.getContextPath().

I did report that upstream because the check is imho wrong in scenarios where the context path is taken from the X-Forwarded-Prefix header to support proxies. https://github.com/thymeleaf/thymeleaf/issues/937 and this is already fixed in: https://github.com/thymeleaf/thymeleaf/issues/926 with 3.1.1-SNAPSHOT.

Comment From: philwebb

At this point I'm not sure we should do anything in Spring Boot. It seems like the Thymeleaf updates or workarounds are generally OK for folks.