Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"file:." cannot be resolved to java.nio.file.Path (and plain "." value resolves to classpath root) #33124

Closed
ewoerner opened this issue Jun 30, 2024 · 11 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: bug A general bug
Milestone

Comments

@ewoerner
Copy link

Given the following stripped-down demo application:

@SpringBootApplication
public class InjectedPathDemoApplication implements ApplicationRunner {
	@Value("${dir}")
	private Path dir;

	public static void main(String[] args) {
		SpringApplication.run(InjectedPathDemoApplication.class, args);
	}

	@Override
	public void run(ApplicationArguments args) throws Exception {
		System.out.println(dir);
	}
}

When run using a JDK in Windows:

$ java -jar demo.jar --dir=..
..
$ java -jar demo.jar --dir=./test
test
$ java -jar demo.jar --dir=.
C:\■■■■■■■■\injected-path-demo\bin\main

When run as native binary compiled with GraalVM:

$ ./demo-native --dir=..
..
$ ./demo-native --dir=./test
test
$ ./demo-native --dir=.
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'injectedPathDemoApplication': Unsatisfied dependency expressed through field 'dir': Failed to convert value of type 'java.lang.String' to required type 'java.nio.file.Path'; Could not retrieve file for class path resource []: class path resource [] cannot be resolved to absolute file path because it does not reside in the file system: resource:/
Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'injectedPathDemoApplication': Unsatisfied dependency expressed through field 'dir': Failed to convert value of type 'java.lang.String' to required type 'java.nio.file.Path'; Could not retrieve file for class path resource []: class path resource [] cannot be resolved to absolute file path because it does not reside in the file system: resource:/
        at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveValue(AutowiredFieldValueResolver.java:194)
        at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveAndSet(AutowiredFieldValueResolver.java:167)
        at com.example.demo.InjectedPathDemoApplication__Autowiring.apply(InjectedPathDemoApplication__Autowiring.java:17)
        at org.springframework.beans.factory.support.InstanceSupplier$1.get(InstanceSupplier.java:83)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:949)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1219)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1162)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:962)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352)
        at com.example.demo.InjectedPathDemoApplication.main(InjectedPathDemoApplication.java:17)
Caused by: org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.nio.file.Path'; Could not retrieve file for class path resource []: class path resource [] cannot be resolved to absolute file path because it does not reside in the file system: resource:/
        at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:87)
        at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:71)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1381)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353)
        at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveValue(AutowiredFieldValueResolver.java:188)
        ... 21 more
Caused by: java.lang.IllegalArgumentException: Could not retrieve file for class path resource []: class path resource [] cannot be resolved to absolute file path because it does not reside in the file system: resource:/
        at org.springframework.beans.propertyeditors.PathEditor.setAsText(PathEditor.java:115)
        at org.springframework.beans.TypeConverterDelegate.doConvertTextValue(TypeConverterDelegate.java:439)
        at org.springframework.beans.TypeConverterDelegate.doConvertValue(TypeConverterDelegate.java:412)
        at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:161)
        at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:80)
        ... 25 more

Expected result for the third call: returns either "." or "", and does not throw an error on native

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jun 30, 2024
@sbrannen
Copy link
Member

Hi @ewoerner,

Congratulations on opening your first issue for the Spring Framework! 👍

What happens if you introduce the file: prefix as follows?

@Value("file:${dir}")

@sbrannen sbrannen added the status: waiting-for-feedback We need additional information before we can continue label Jun 30, 2024
@sbrannen sbrannen changed the title Strange value injection of "." into path Strange @Value injection of "." into Path in native image Jun 30, 2024
@ewoerner
Copy link
Author

@sbrannen that causes injection to fail with any value, with the following stacktrace:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'injectedPathDemoApplication': Unsatisfied dependency expressed through field 'dir': Failed to convert value of type 'java.lang.String' to required type 'java.nio.file.Path'; URI is not hierarchical
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:787) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:767) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:508) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1421) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:962) ~[spring-context-6.1.10.jar:6.1.10]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) ~[spring-context-6.1.10.jar:6.1.10]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.1.jar:3.3.1]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.1.jar:3.3.1]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.1.jar:3.3.1]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.1.jar:3.3.1]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.1.jar:3.3.1]
	at com.example.demo.InjectedPathDemoApplication.main(InjectedPathDemoApplication.java:17) ~[main/:na]
Caused by: org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.nio.file.Path'; URI is not hierarchical
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:87) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:71) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1381) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:784) ~[spring-beans-6.1.10.jar:6.1.10]
	... 19 common frames omitted
Caused by: java.lang.IllegalArgumentException: URI is not hierarchical
	at java.base/sun.nio.fs.WindowsUriSupport.fromUri(WindowsUriSupport.java:123) ~[na:na]
	at java.base/sun.nio.fs.WindowsFileSystemProvider.getPath(WindowsFileSystemProvider.java:98) ~[na:na]
	at java.base/java.nio.file.Path.of(Path.java:203) ~[na:na]
	at java.base/java.nio.file.Paths.get(Paths.java:98) ~[na:na]
	at org.springframework.beans.propertyeditors.PathEditor.setAsText(PathEditor.java:86) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.TypeConverterDelegate.doConvertTextValue(TypeConverterDelegate.java:439) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.TypeConverterDelegate.doConvertValue(TypeConverterDelegate.java:412) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:161) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:80) ~[spring-beans-6.1.10.jar:6.1.10]
	... 23 common frames omitted


@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jun 30, 2024
@sbrannen
Copy link
Member

How about @Value("file://${dir}")?

@ewoerner
Copy link
Author

That also throws, but with a different root cause:

[...]
Caused by: java.lang.IllegalArgumentException: URI path component is empty
	at java.base/sun.nio.fs.WindowsUriSupport.fromUri(WindowsUriSupport.java:133) ~[na:na]
	at java.base/sun.nio.fs.WindowsFileSystemProvider.getPath(WindowsFileSystemProvider.java:98) ~[na:na]
	at java.base/java.nio.file.Path.of(Path.java:203) ~[na:na]
	at java.base/java.nio.file.Paths.get(Paths.java:98) ~[na:na]
	at org.springframework.beans.propertyeditors.PathEditor.setAsText(PathEditor.java:86) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.TypeConverterDelegate.doConvertTextValue(TypeConverterDelegate.java:439) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.TypeConverterDelegate.doConvertValue(TypeConverterDelegate.java:412) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:161) ~[spring-beans-6.1.10.jar:6.1.10]
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:80) ~[spring-beans-6.1.10.jar:6.1.10]
	... 23 common frames omitted

@sbrannen
Copy link
Member

Thanks for trying out both of those variants and providing feedback!

I unfortunately don't work on a Windows machine, so I don't have an easy way to test this. Though, perhaps one of my colleagues can test this on Windows.

In the interim, please let us know which versions of the following you are using.

  • MS Windows
  • JDK
  • GraalVM

From the stack trace, I can deduce that you're using Spring Boot 3.3.1 with Spring Framework 6.1.10.

@sbrannen sbrannen added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Jun 30, 2024
@snicoll
Copy link
Member

snicoll commented Jul 1, 2024

There's not much Spring is doing around that call stack. Since you can easily reproduce, it would be helpful to see if the raw Java API usage throws that exception as well. Can you please replace the @Value injection by the following in your run method?

Paths.get(URI.create("."));

If this fails as well, we know we can close this in favour of a report in GraalVM itself.

@jhoeller jhoeller added the in: core Issues in core modules (aop, beans, core, context, expression) label Jul 1, 2024
@ewoerner
Copy link
Author

ewoerner commented Jul 2, 2024

Version info, as requested:
Windows 11
OpenJDK Runtime Environment GraalVM CE 17.0.9+9.1 (build 17.0.9+9-jvmci-23.0-b22)
OpenJDK Runtime Environment Temurin-21.0.2+13 (build 21.0.2+13-LTS)

Turns out I did not test properly (I ran it from Eclipse), the exception is also reproducible without GraalVM when built as a JAR file:

$ java -jar .\injected-path-demo-1-0.0.1-SNAPSHOT.jar --dir=.
[...]
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'injectedPathDemoApplication': Unsatisfied dependency expressed through field 'dir': Failed to convert value of type 'java.lang.String' to required type 'java.nio.file.Path'; Could not retrieve file for class path resource []: class path resource [] cannot be resolved to absolute file path because it does not reside in the file system: jar:nested:/C:/■■■■■■■■/injected-path-demo-1/build/libs/injected-path-demo-1-0.0.1-SNAPSHOT.jar/!BOOT-INF/classes/!/
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:787) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:767) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:508) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1421) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:962) ~[spring-context-6.1.10.jar!/:6.1.10]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) ~[spring-context-6.1.10.jar!/:6.1.10]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.1.jar!/:3.3.1]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.1.jar!/:3.3.1]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.1.jar!/:3.3.1]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.1.jar!/:3.3.1]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.1.jar!/:3.3.1]
        at com.example.demo.InjectedPathDemoApplication.main(InjectedPathDemoApplication.java:17) ~[!/:0.0.1-SNAPSHOT]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
        at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:91) ~[injected-path-demo-1-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
        at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:53) ~[injected-path-demo-1-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
        at org.springframework.boot.loader.launch.JarLauncher.main(JarLauncher.java:58) ~[injected-path-demo-1-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
Caused by: org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.nio.file.Path'; Could not retrieve file for class path resource []: class path resource [] cannot be resolved to absolute file path because it does not reside in the file system: jar:nested:/C:/■■■■■■■■/injected-path-demo-1/build/libs/injected-path-demo-1-0.0.1-SNAPSHOT.jar/!BOOT-INF/classes/!/
        at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:87) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:71) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1381) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:784) ~[spring-beans-6.1.10.jar!/:6.1.10]
        ... 26 common frames omitted
Caused by: java.lang.IllegalArgumentException: Could not retrieve file for class path resource []: class path resource [] cannot be resolved to absolute file path because it does not reside in the file system: jar:nested:/C:/■■■■■■■■/injected-path-demo-1/build/libs/injected-path-demo-1-0.0.1-SNAPSHOT.jar/!BOOT-INF/classes/!/
        at org.springframework.beans.propertyeditors.PathEditor.setAsText(PathEditor.java:115) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.TypeConverterDelegate.doConvertTextValue(TypeConverterDelegate.java:439) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.TypeConverterDelegate.doConvertValue(TypeConverterDelegate.java:412) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:161) ~[spring-beans-6.1.10.jar!/:6.1.10]
        at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:80) ~[spring-beans-6.1.10.jar!/:6.1.10]
        ... 30 common frames omitted

So the chain seems to be as follows:

  • Since the value does not start with a "/" and has no scheme, the PathEditor directly delegates its work to the ResourceEditor
  • The ResourceEditor resolves the resource to a DefaultResourceLoader.ClassPathContextResource
  • The PathEditor checks whether the resource exists; this is true for "."
  • Since the resource exists, the PathEditor calls AbstractFileResolvingResource.getFile() which only supports "vfs" and "file" protocol. However, the protocol is "jar" (with JAR) or "resource" (with GraalVM native binary).

The error also happens with any other value that resolves to an existing resource, so --dir=application.properties also throws an Exception.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jul 2, 2024
@snicoll snicoll changed the title Strange @Value injection of "." into Path in native image Existing path cannot be resolved from String value on Microsoft Windows Jul 3, 2024
@jhoeller
Copy link
Contributor

jhoeller commented Jul 3, 2024

Without a prefix, PathEditor tries to resolve a given path as context-relative which typically means the classpath. And in the classpath, we cannot resolve jar-contained resources as a file system reference and therefore not expose them as a java.nio.file.Path. As a consequence, resolving application.properties or any other existing classpath resource cannot work that way in any case. From that perspective, the behavior is as expected.

Now, with a "." reference specifically, we may debate what that semantically means. According to the PathEditor algorithm with a classpath-first search, it effectively means the nearest classpath root - and if that happens to be in a jar file, we cannot resolve it in the file system either. On the other hand, resolving the nearest classpath root as a java.nio.file.Path arguably never makes practical sense, not even for classpath layouts in the file system.

PathEditor acquired quite a few twists for plain paths over the years, and I suppose we could add one more for directory references which never make sense in the classpath. That said, generally speaking, it is preferable to specify a concrete path with a prefix so that the resolution algorithm reliably knows what it is supposed to mean.

@snicoll
Copy link
Member

snicoll commented Jul 3, 2024

I thought we did try to reproduce before asking you to change anything. Turns out that it fails on MacOS as well:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'demoPathApplication': Unsatisfied dependency expressed through field 'dir': Failed to convert value of type 'java.lang.String' to required type 'java.nio.file.Path'; Could not retrieve file for class path resource []: class path resource [] cannot be resolved to absolute file path because it does not reside in the file system: jar:nested:/Users/snicoll/Downloads/demo-path/build/libs/demo-path-0.0.1-SNAPSHOT.jar/!BOOT-INF/classes/!/
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:787) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:767) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:508) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1421) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:962) ~[spring-context-6.1.10.jar!/:6.1.10]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) ~[spring-context-6.1.10.jar!/:6.1.10]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.1.jar!/:3.3.1]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.1.jar!/:3.3.1]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.1.jar!/:3.3.1]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.1.jar!/:3.3.1]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.1.jar!/:3.3.1]
	at com.example.demopath.DemoPathApplication.main(DemoPathApplication.java:18) ~[!/:0.0.1-SNAPSHOT]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:91) ~[demo-path-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
	at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:53) ~[demo-path-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
	at org.springframework.boot.loader.launch.JarLauncher.main(JarLauncher.java:58) ~[demo-path-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
Caused by: org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.nio.file.Path'; Could not retrieve file for class path resource []: class path resource [] cannot be resolved to absolute file path because it does not reside in the file system: jar:nested:/Users/snicoll/Downloads/demo-path/build/libs/demo-path-0.0.1-SNAPSHOT.jar/!BOOT-INF/classes/!/
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:87) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:71) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1381) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:784) ~[spring-beans-6.1.10.jar!/:6.1.10]
	... 26 common frames omitted
Caused by: java.lang.IllegalArgumentException: Could not retrieve file for class path resource []: class path resource [] cannot be resolved to absolute file path because it does not reside in the file system: jar:nested:/Users/snicoll/Downloads/demo-path/build/libs/demo-path-0.0.1-SNAPSHOT.jar/!BOOT-INF/classes/!/
	at org.springframework.beans.propertyeditors.PathEditor.setAsText(PathEditor.java:115) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.TypeConverterDelegate.doConvertTextValue(TypeConverterDelegate.java:439) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.TypeConverterDelegate.doConvertValue(TypeConverterDelegate.java:412) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:161) ~[spring-beans-6.1.10.jar!/:6.1.10]
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:80) ~[spring-beans-6.1.10.jar!/:6.1.10]
	... 30 common frames omitted

Sorry about that @ewoerner. So yeah, the bottom line is that you can't get a Path from a classpath resource because there's no such file to begin with on the file system.

@snicoll snicoll changed the title Existing path cannot be resolved from String value on Microsoft Windows Path cannot be resolved from String value when referring to a classpath element Jul 3, 2024
@jhoeller
Copy link
Contributor

jhoeller commented Jul 3, 2024

In general, the proper solution is to specify a "file:" prefix for an explicit file system reference there. That should also work for "file:." (as suggested above) but unfortunately doesn't, in contrast to java.io.File references processed by FileEditor. We might turn this issue into a bug ticket for that "file:." purpose, making it consistently work across File and Path.

@jhoeller jhoeller changed the title Path cannot be resolved from String value when referring to a classpath element "file:." cannot be resolved to java.nio.file.Path (and plain "." value resolves to classpath root) Jul 3, 2024
@jhoeller jhoeller added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Jul 3, 2024
@jhoeller jhoeller self-assigned this Jul 3, 2024
@jhoeller jhoeller added this to the 6.1.11 milestone Jul 3, 2024
@jhoeller
Copy link
Contributor

jhoeller commented Jul 3, 2024

We're catching the IllegalArgumentException for non-hierarchical URIs now, letting the regular resource resolution apply which is capable of resolving file:. already. Also, we're providing a more meaningful exception for our own file-resolution IllegalArgumentException now.

jhoeller added a commit that referenced this issue Jul 3, 2024
Includes meaningful exception message for file system resolution.

Closes gh-33124

(cherry picked from commit daea3f0)
jhoeller added a commit that referenced this issue Jul 3, 2024
Includes meaningful exception message for file system resolution.

Closes gh-33124

(cherry picked from commit daea3f0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: bug A general bug
Projects
None yet
Development

No branches or pull requests

5 participants