fix(spring): handle Unicode classpath resource paths (#24220)(CP:25.0)#24250
Merged
Conversation
Fix Spring classpath resource matching when an application is located under a directory whose path contains encoded Unicode or decomposed Unicode characters. `CustomResourceLoader` previously compared resource paths from `URL#getPath()`, which can leave parts of the classpath root percent-encoded. When Spring later returned class resources using a decoded path, the resource no longer matched its parent root and startup could fail with `Parent resource ... not found in the resources!`. This can happen even when both paths refer to the same filesystem location. The issue is that the compared strings are not in the same representation: - `URL#getPath()` can return a path where non-ASCII characters are still percent-encoded, for example `%C3%A7` or `%CC%A7`. - Spring resource resolution can later return the corresponding class resource using a decoded filesystem path. - A raw string comparison then fails, even though both paths point to the same location. This is especially easy to reproduce with decomposed Unicode characters. A decomposed character is represented as a base character plus one or more combining marks instead of a single precomposed code point. For example, `ç` can be represented either as the single code point `U+00E7`, or as `c` plus the combining cedilla `U+0327`. Visually the path can look correct, but the encoded URL path and the decoded filesystem path are different strings. The culprit was therefore not Unicode normalization itself, but comparing URL-encoded paths with decoded paths during parent/child classpath resource matching. This change normalizes resource paths through `URL#toURI().getPath()` for comparisons, while keeping the original URL path for dev-mode cache keys. It also keeps the existing native-image `file:///resources!` handling and falls back to the original URL path if URI conversion is not possible. The key piece needed to make the fix work is using the decoded URI path for comparable resource paths: ```java resource.getURL().toURI().getPath() ``` instead of relying on the raw URL path for matching: ```java resource.getURL().getPath() ``` This follows the JDK recommendation for URL escaping handling. `URL` does not itself encode or decode URL components, and the recommended way to manage URL encoding and decoding is to use `URI` and convert between `URL` and `URI`. Recommended reference: https://docs.oracle.com/javase/8/docs/api/java/net/URL.html Closes #11871 The regression test needs to exercise `CustomResourceLoader` directly. I found existing Flow tests using both patterns: some use reflection to reach private implementation details, and others keep implementation types package-private so same-package tests can instantiate them directly. I chose to make `CustomResourceLoader` package-private instead of using reflection, because it keeps the test simpler while still avoiding public API exposure. If the maintainers prefer preserving the private nested class, I can switch the test back to reflection and make `CustomResourceLoader` private again. The important behavioral change is limited to the comparable path used for parent/child resource matching. The original URL path is still preserved where the previous encoded form is required, such as dev-mode cache lookup keys. - Reproduced the issue with a minimal Spring Boot/Vaadin application in a project path containing `François` and a decomposed Unicode segment. Before the fix, the app failed during test startup with `Parent resource ... not found in the resources!`. - Verified the same minimal application starts/tests successfully after installing the fixed local Flow artifacts. - Added regression coverage in `VaadinServletContextInitializerTest` for a classpath root containing Unicode and decomposed Unicode characters. - Verified the regression test is meaningful: with only the production fix temporarily reverted, the new test fails with `Parent resource ... not found in the resources!`; with the fix restored, the same test passes. Local checks run: ```bash mvn -q -P!install-git-hooks -pl vaadin-spring spotless:check mvn -q -P!install-git-hooks -pl vaadin-spring -Dtest=VaadinServletContextInitializerTest test mvn -q -P!install-git-hooks -pl vaadin-spring -Dtest=VaadinServletContextInitializerTest#customResourceLoader_classpathRootContainsUnicodeCombiningCharacter_resourcesAreMatched test mvn -q -P!install-git-hooks -pl vaadin-spring -am -DskipTests -Dexec.skip=true install ``` Code drafted with OpenClaw/Codex for contributor review. Tests were added and run locally by the assistant. I reviewed the code and this description before opening the PR. Co-authored-by: Mikhail Shabarov <61410877+mshabarov@users.noreply.github.com>
|
platosha
approved these changes
May 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Cherry pick of #24220 to 25.0