Repository navigation
Description
Affects: main and 7.0.x. The cause has been in StandaloneMockMvcBuilder since at least 5.3.x.
Calling build() twice on a StandaloneMockMvcBuilder or RouterFunctionMockMvcBuilder fails on the second call:
StandaloneMockMvcBuilder builder = MockMvcBuilders.standaloneSetup(new HelloController());
builder.build();
builder.build(); // throwsorg.springframework.context.ApplicationContextException: Cannot reinitialize with different application context:
current one is [...StubWebApplicationContext@79d63a4f], passed-in one is [...StubWebApplicationContext@2d74cbbd]
at ...StandaloneMockMvcBuilder.initViewResolvers(StandaloneMockMvcBuilder.java:427)
This happens even if the test never configures a view resolver. initViewResolvers assigns the default resolver back to the builder field:
this.viewResolvers = (this.viewResolvers != null ? this.viewResolvers :
Collections.singletonList(new InternalResourceViewResolver()));On the second build(), that cached InternalResourceViewResolver is given a new StubWebApplicationContext, and ApplicationObjectSupport rejects it. RouterFunctionMockMvcBuilder has the same code.
Most tests build once, but RestTestClient makes the problem easier to hit. RestTestClient.bindToController(...) and bindToRouterFunction(...) build a new MockMvc on every build(), so this fails the same way:
RestTestClient.Builder<?> builder = RestTestClient.bindToController(new HelloController());
builder.build();
builder.build().get().uri("/").exchange().expectStatus().isOk(); // ApplicationContextExceptionUsing a local variable in initViewResolvers in both builders, instead of assigning the field, fixes it. With that change, both snippets above pass, and so do the existing org.springframework.test.web.servlet.setup tests.
Related: built MockMvc instances share the builder's result matchers
Once the second build works, a second problem shows. AbstractMockMvcBuilder.build() passes its own globalResultMatchers and globalResultHandlers lists to the new MockMvc, and MockMvc stores them without copying. So alwaysExpect(...) / alwaysDo(...) calls made after build() also apply to MockMvc instances built earlier:
StandaloneMockMvcBuilder builder = MockMvcBuilders.standaloneSetup(new HelloController())
.setViewResolvers((viewName, locale) -> null); // works around the problem above
MockMvc first = builder.build();
builder.alwaysExpect(status().isNotFound()).build();
first.perform(get("/")).andExpect(status().isOk());
// AssertionError: Status expected:<404> but was:<200>This is the same kind of problem as the recent HttpServiceProxyFactory.Builder fix (copy the lists in build()). build() also changes the shared defaultRequestBuilder in place. A MockMvcConfigurer's RequestPostProcessor is added to it with with(...) on every build, so the post-processors pile up.
If you agree these should be fixed, I'd like to submit a PR covering both builders with tests. Would you prefer the result matcher/handler copying in the same PR, or as a separate issue?
Activity
Orcus01 commentedon Nov 6, 2026
I’d like to help reproduce this on main while it’s in triage. I can check two consecutive build() calls for both MockMvc builders and see whether the same failure occurs through RestTestClient. Would a minimal reproduction or focused regression test help with triage?
shinseongsu commentedon Nov 7, 2026
Thanks @Orcus01, that would help.
A check on
mainfor both builders, and throughRestTestClient, would confirm the first part. This is the fix I have in mind:initViewResolversinStandaloneMockMvcBuilderandRouterFunctionMockMvcBuilder: keep the default resolver in a local variable instead of assigning it to the field.AbstractMockMvcBuilder.build(): pass copies ofglobalResultMatchersandglobalResultHandlerstoMockMvc, as in theHttpServiceProxyFactory.Builderfix.The part I haven't settled is
defaultRequestBuilder.build()adds the configurers'RequestPostProcessors to it in place, so they pile up across builds. I'm not sure yet whether to copy it inbuild()or to apply the post-processors to a fresh instance each time. If you notice something there, or another builder with the same pattern, please share it.I'll take care of the PR once the team agrees on the direction, so we don't end up with two PRs for the same change.
Orcus01 commentedon Nov 8, 2026
I reproduced this on main at
f95aca0using JDK 25 and the Spring Gradle test task. In each case the firstbuild()succeeded and the second threwApplicationContextException: Cannot reinitialize with different application context:MockMvcBuilders.standaloneSetup(controller)MockMvcBuilders.routerFunctions(routes)RestTestClient.bindToController(controller)RestTestClient.bindToRouterFunction(routes)Each test used a fresh builder with the default view resolver and called
build()twice on that same instance. The controller and router both expose a simple GET endpoint. The focused command was./gradlew :spring-test:test --tests '*RepeatedMockMvcBuildTests'.