diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java index 40547cde721e..ea0ad54b056a 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java @@ -858,6 +858,8 @@ protected Set doFindPathMatchingJarResources(Resource rootDirResource, Set result = new LinkedHashSet<>(64); // Clean root entry path to match jar entries format without "!" separators rootEntryPath = rootEntryPath.replace(ResourceUtils.JAR_URL_SEPARATOR, "/"); + // Normalize to match cached entries (which have already been normalized) + rootEntryPath = normalizeJarEntryPath(rootEntryPath); // Search sorted entries from first entry with rootEntryPath prefix boolean rootEntryPathFound = false; for (String entryPath : entriesCache.tailSet(rootEntryPath, false)) { @@ -936,11 +938,7 @@ protected Set doFindPathMatchingJarResources(Resource rootDirResource, NavigableSet entriesCache = new TreeSet<>(); Iterator entryIterator = jarFile.stream().map(JarEntry::getName).sorted().iterator(); while (entryIterator.hasNext()) { - String entryPath = entryIterator.next(); - int entrySeparatorIndex = entryPath.indexOf(ResourceUtils.JAR_URL_SEPARATOR); - if (entrySeparatorIndex >= 0) { - entryPath = entryPath.substring(entrySeparatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length()); - } + String entryPath = normalizeJarEntryPath(entryIterator.next()); entriesCache.add(entryPath); if (entryPath.startsWith(rootEntryPath)) { String relativePath = entryPath.substring(rootEntryPath.length()); @@ -980,6 +978,26 @@ protected JarFile getJarFile(String jarFileUrl) throws IOException { } } + /** + * Normalize a JAR entry path by stripping any prefixes that may prevent proper matching. + *

This handles nested JAR separators and Spring Boot fat JAR structure where classes + * are stored under {@code BOOT-INF/classes/}. + * @param entryPath the raw JAR entry path to normalize + * @return the normalized entry path suitable for pattern matching + */ + private String normalizeJarEntryPath(String entryPath) { + // Strip nested JAR separator if present + int entrySeparatorIndex = entryPath.indexOf(ResourceUtils.JAR_URL_SEPARATOR); + if (entrySeparatorIndex >= 0) { + entryPath = entryPath.substring(entrySeparatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length()); + } + // Strip Spring Boot fat JAR prefix (BOOT-INF/classes/) + if (entryPath.startsWith("BOOT-INF/classes/")) { + entryPath = entryPath.substring("BOOT-INF/classes/".length()); + } + return entryPath; + } + /** * Find all resources in the file system of the supplied root directory that * match the given location sub pattern via the Ant-style {@link #getPathMatcher()