There are two ways to configure Spring MVC resources - via the spring.web.resources configuration, and the deprecated spring.resources configuration.

With spring-boot 2.4.0, there is a bug where if Devtools is enabled, spring.web.resources is ignored and the deprecated configuration is read instead.

The WebMvcAutoConfiguration ostensibly only chooses the deprecated configuration if it's been customized, and falls back to the WebProperties Resources. However, one of Devtools' customizations (disabling spring.resources.chain.cache) sets the "has been customized" flag and thus switches configurations out from under you.

I noticed this because static resources started 404'ing with Devtools enabled, but worked fine without. Switching to the deprecated configuration fixes the issue, but is clearly an undesirable solution.

This can be observed in the following test case. Note that Devtools turns itself off in tests and this can't be bypassed, but setting the spring.resources.chain.cache=false simulates its modification.

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
  properties = {
    "spring.web.resources.static-locations=file:" + DevToolsWebResourceTest.tmpDir,
    // uncomment the following line to simulate devtools' change and break static resource resolution
    // "spring.resources.chain.cache=false"
  }
)
class DevToolsWebResourceTest {
  static final String tmpDir = "/tmp";
  private static final String content = "Hello, world";
  private static final String resource = "hello" + System.currentTimeMillis() + ".txt";
  private static final Path path = Path.of(tmpDir, resource);

  @BeforeAll
  static void setUp() throws Exception {
    Files.writeString(path, content);
  }

  @AfterAll
  static void tearDown() throws Exception {
    Files.delete(path);
  }

  @LocalServerPort
  private int port;

  @Autowired
  private TestRestTemplate restTemplate;

  @Test
  void staticResource() {
    final String s = restTemplate
        .getForObject("http://localhost:" + port + "/" + resource, String.class);
    assertEquals(content, s);
  }
}

Comment From: wilkinsona

I think this may be a duplicate of #24203.

Comment From: snicoll

I thought the same Andy but this test is failing for me with 2.4.1-SNAPSHOT.

Comment From: snicoll

Courtesy of @wilkinsona for reminding me. With spring.web.resources.static-locations and spring.web.resources.chain.cache this test passes. You can't mix and match deprecated properties with the new namespace. So it looks like indeed this was fixed via #24203.

@chrisrhut please give your actual project a try with 2.4.1-SNAPSHOT. If that still failing we can reopen but we'd need more details. Thanks!

Comment From: chrisrhut

Thank you very much for the quick response, @wilkinsona and @snicoll - I confirmed 2.4.1-SNAPSHOT works in my application :pray:

Comment From: snicoll

Thank you very much for checking @chrisrhut.