diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHandlerUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHandlerUtils.java
index fa091ef2eb6f..acfe98f729d4 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHandlerUtils.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHandlerUtils.java
@@ -38,6 +38,7 @@
* {@link ResourceHttpRequestHandler} and {@link org.springframework.web.servlet.function}.
*
* @author Rossen Stoyanchev
+ * @author Edoardo Patti
* @since 6.2
*/
public abstract class ResourceHandlerUtils {
@@ -81,6 +82,23 @@ public static void assertLocationPath(@Nullable String path) {
"Resource location does not end with slash: " + path);
}
+ /**
+ * Assert the given location path is not null and look for the trailing slash adding it when absent.
+ * @return the input string if the trailing slash was already present
+ * the input string plus the trailing slash otherwise.
+ */
+ public static String addTrailingSlashIfAbsent(@Nullable String path) {
+ Assert.notNull(path, "Resource location path must not be null");
+ if(!path.endsWith(FOLDER_SEPARATOR) && !path.endsWith(WINDOWS_FOLDER_SEPARATOR)) {
+ logger.warn(
+ "Observed an attempting to register a resource with a location without trailing slash: '%s'. The framework will add it automatically."
+ .formatted(path));
+ var trailingSlash = path.contains(WINDOWS_FOLDER_SEPARATOR) ? WINDOWS_FOLDER_SEPARATOR : FOLDER_SEPARATOR;
+ return path.concat(trailingSlash);
+ }
+ return path;
+ }
+
/**
* Normalize the given resource path replacing the following:
*
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
index 836ab6f9a638..45a9fcef82e4 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
@@ -496,7 +496,7 @@ private void resolveResourceLocations() {
charset = Charset.forName(value);
location = location.substring(endIndex + 1);
}
- ResourceHandlerUtils.assertLocationPath(location);
+ location = ResourceHandlerUtils.addTrailingSlashIfAbsent(location);
Resource resource = applicationContext.getResource(location);
if (location.equals("/") && !(resource instanceof ServletContextResource)) {
throw new IllegalStateException(
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHandlerUtilsTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHandlerUtilsTests.java
new file mode 100644
index 000000000000..da6991a1f9b3
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHandlerUtilsTests.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.resource;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link ResourceHandlerUtils}.
+ *
+ * @author Edoardo Patti
+ */
+public class ResourceHandlerUtilsTests {
+
+ @Test
+ void testTrailingSlash() {
+ var windowsDirectory = "META-INF\\resources\\webjars";
+ var directory = "META-INF/resources/webjars";
+ var directoryWithoutSlash = "META-INF";
+ var correctWindowsDirectory = "META-INF\\resources\\webjars\\";
+ var correctDirectory = "META-INF/resources/webjars/";
+ assertThat(ResourceHandlerUtils.addTrailingSlashIfAbsent(windowsDirectory).endsWith("\\")).isTrue();
+ assertThat(ResourceHandlerUtils.addTrailingSlashIfAbsent(directory).endsWith("/")).isTrue();
+ assertThat(ResourceHandlerUtils.addTrailingSlashIfAbsent(directoryWithoutSlash).endsWith("/")).isTrue();
+ assertThat(ResourceHandlerUtils.addTrailingSlashIfAbsent(correctWindowsDirectory)).isEqualTo(correctWindowsDirectory);
+ assertThat(ResourceHandlerUtils.addTrailingSlashIfAbsent(correctDirectory)).isEqualTo(correctDirectory);
+ }
+}