I was applied a latest Spring 6 snapshot version, I found the PathMatchingResourcePatternResolver handles path names differently than Spring 5 latest GA version(5.3.23) on Mac OS. For example, If the file name contains voiced sound characters (e.g. ), the result will be different.

Is this behavior by design or a regression bug as implemented in Spring 6? If this behavior was designed, I will rewrite my library to adjust Spring 6 specification.

Reproducing

Directory structures:

...
└── target
    └── test-classes
        └── org
            └── example
                └── バリューオブジェクト
                    └── ジドウシャ.class

Test Cases:

package org.example;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.io.IOException;
import java.util.stream.Collectors;

class AppTest {
  String path;

  @BeforeEach
  void setup() throws IOException {
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    Resource resource = resolver.getResources("classpath*:org/example/バリューオブジェクト/**/*.class")[0];
    path = resource.getURL().toString().replaceFirst(".*/test-classes/", "");
    System.out.println(path);
    System.out.println(path.length());
  }

  @Test
  void successRunningOnSpring5() {
    // o  r  g  /  e  x  a  m  p  l  e  /  バ        リ    ュ   ー   オ    ブ        ジ        ェ    ク   ト    /  ジ        ド        ウ    シ   ャ    .  c  l  a  s  s
    // 6f,72,67,2f,65,78,61,6d,70,6c,65,2f,30d0     ,30ea,30e5,30fc,30aa,30d6     ,30b8     ,30a7,30af,30c8,2f,30b8     ,30c9     ,30a6,30b7,30e3,2e,63,6c,61,73,73
    System.out.println(path.chars().mapToObj(Integer::toHexString).collect(Collectors.joining(",")));

    Assertions.assertEquals(34, path.length());
    Assertions.assertEquals("org/example/バリューオブジェクト/ジドウシャ.class", path);
  }

  @Test
  void successRunningOnSpring6() {
    // o  r  g  /  e  x  a  m  p  l  e  /  ハ    ゙  リ    ュ   ー   オ    ブ        シ    ゙   ェ    ク   ト    /  シ   ゙   ト   ゙    ウ    シ   ャ    .  c  l  a  s  s
    // 6f,72,67,2f,65,78,61,6d,70,6c,65,2f,30cf,3099,30ea,30e5,30fc,30aa,30d5,3099,30b7,3099,30a7,30af,30c8,2f,30b7,3099,30c8,3099,30a6,30b7,30e3,2e,63,6c,61,73,73
    System.out.println(path.chars().mapToObj(Integer::toHexString).collect(Collectors.joining(",")));

    Assertions.assertEquals(39, path.length());
    Assertions.assertEquals("org/example/ハ\u3099リューオフ\u3099シ\u3099ェクト/シ\u3099ト\u3099ウシャ.class", path);
  }
}

Repro project

https://github.com/kazuki43zoo/repro-spring-project-gh-29243

How to run with Spring 5 GA version:

./mvnw clean test
  • AppTest#successRunningOnSpring5 : success
  • AppTest#successRunningOnSpring6 : fail

How to run with Spring 6 snapshot version:

./mvnw clean test -Pspring6
  • AppTest#successRunningOnSpring5 : fail
  • AppTest#successRunningOnSpring6 : sucess

Comment From: bclozel

Thanks for reporting this @kazuki43zoo this is probably linked to #29163

Comment From: kazuki43zoo

Note: Occurred on Mac OS and Windows OS. Linux OS works fine.

Comment From: kazuki43zoo

Windows OS is another error. Probably it's same with https://github.com/spring-projects/spring-framework/issues/29226.

Comment From: sbrannen

Hi @kazuki43zoo,

Thanks for bringing this to our attention.

My initial analysis shows that there are two issues here.

  1. The path is URL-encoded.
  2. The path is not in normalized Unicode form.

You can address # 1 by accessing the URL-decoded path via the URI instead of the URL as follows.

path = resource.getURI().getPath().replaceFirst(".*/test-classes/", "");

As stated in the Javadoc for java.net.URL, you can use java.net.URI to avoid issues with URL-encoding of the path (URI#getPath vs. URI#getRawPath).


You can address # 2 by normalizing the path using java.text.Normalizer as follows.

path = Normalizer.normalize(path, Normalizer.Form.NFC);

Please let us know if those modifications to your test allow the test to pass.

In addition, I will investigate if we can align with the behavior in Spring Framework 5.3.x.

Comment From: sbrannen

Team Decision:

After further consideration, we have decided that the use of Resource#getURL and Resource#getURI should be avoided when performing direct comparisons against paths in a file system since those methods make no guarantees about the encoding of the paths with regard to the underlying file system.

Instead, if you wish to perform a direct comparison against a path in a Resource, you should use either the File or Path abstraction.

For example, if you change your original code to the following your tests should pass.

path = resource.getFile().getAbsolutePath().replaceFirst(".*/test-classes/", "");

In light of the above, we are closing this issue.

Comment From: sbrannen

As a side note, if you still need to normalize paths you might find that your assertion library provides such support.

For example, AssertJ provides assertThat(path).isEqualToNormalizingUnicode(...).

Comment From: kazuki43zoo

@sbrannen Thanks for good advice!!