Consider using a windows absolute path in a property file:
repository.directory= C:some_path
We want to inject directly a Path using @Value:
public MyRepository(@Value("${repository.directory}") Path repositoryDirectory) {
this.repositoryDirectory = repositoryDirectory;
}
- If we are using the original Windows path, with back-slash:
repository.directory=C:\Users\lacasoub\myUser\Local\Temp
Some class (Properties.load() i guess) trims the slash and we end up with the following string: C:UsersmyUserAppDataLocalTemp. Which is then concat with the Sprint boot server relative path, ending with a String like this: C:\Users\myUser\AppData\Local\Temp\undertow-docbase.2615281017312972350.8080\C:UsersmyUserAppDataLocalTemp
Its a normal behavior for properties file.
-
If we are using double back-slash, it works.
-
If we are using slash instead:
repository.directory=C:/Users/myUser/AppData/Local/Temp
PathEditor has the following behavior:
- Converting this string to an URI, which is strange because URI and Windows Path are not compliant.
- Adding a "/" because it don't understand that's it's a Windows path
- Concat the relative path /C:/Users/myUser/AppData/Local/Temp with the Sprint boot server relative path, ending with a String like this: C:\Users\myUser\AppData\Local\Temp\undertow-docbase.2615281017312972350.8080\C:\Users\myUser\AppData\Local\Temp
- Java path API throws an exception because the path creating by Sprint Boot PathEditor class is invalid:
Caused by: java.nio.file.InvalidPathException: Illegal char <:> at index
The solution 2 works but the solution 3 should also works as the Java Path class allows Windows absolute path with back-slash (Linux style) and not the PathEditor.
Comment From: lgxbslgx
@aymeric-soubrouillard I don't quite understand your question. It confuse me in many aspects so that I can't reproduce your problem.
Sprint boot server
Do you mean spring boot? And you would better to indicate the version of the spring you use and the version of the spring boot if used.
Which is then concat with the Sprint boot server relative path
Why do you want to concatenate a absolute path with another path? I can't understand what you want to do.
Maybe we have trouble communicating in English? And could you give more information, such as a demo, about your problem? A runnable program could make your description clearer.
Comment From: nkjackzhang
'\' means LF in properties files, so Properties.load() will omit it, the result of course is C:UsersmyUserAppDataLocalTemp. The usage of '\\' is the right way.
Comment From: Antoniossss
The case is simple:
PathEditor#setAsText is used to convert String into Path.
On widows, flow will go the way that path will be treated as RESOURCE and will fail to resolve.
It will work, if provided path will start with file:// uri scheme.
Following block breaks functionality wit hexample of text="c:/widows/someFile
URI uri = new URI(text);
if (uri.getScheme() != null) {
nioPathCandidate = false;
// Let's try NIO file system providers via Paths.get(URI)
setValue(Paths.get(uri).normalize());
return;
}
}
uri.getScheme() will return C as scheme, causing nioPathCandidate to be set to false and fails later conversion at
else if (!resource.exists() && nioPathCandidate) {
since it is no longer nioPathCandidate and Paths.get(String) call is ommited - which is required
Comment From: xak2000
I want to add some additional info to this issue.
Maybe it is not directly related to the PathEditor
, but the problem still worth to mention.
The problem is related to integration testing.
Even if I write Windows path "correctly" in test properties file, when I run spring integration test, it binds it wrongly, adding src/main/webapp/
at the start of my path for some reason and the test fails to start...
Example:
appliation-test.properties
:
myapp.local-directory=${java.io.tmpdir}/subdir/attachments
(This path is intended to become /tmp/subdir/attachments
on Unix and C:\Users\myuser\AppData\Local\Temp\subdir\attachments
on Windows. The Path
methods are pretty tolerant to forward/back slash replacement on Windows. Even duplicate slash is not a problem. So to be honest the String
version of this path on Windows will be C:\Users\myuser\AppData\Local\Temp\/subdir/attachments
, but Paths.get(..)
is smart enough to correct all these slashes to right version for the OS and remove duplicates)
@ConfigurationProperties
class:
@Component
@ConfigurationProperties(prefix = "myapp")
public class MyProps {
private Path localDirectory;
}
When I run the app on Windows machine it correctly binds path C:\Users\myuser\AppData\Local\Temp\subdir\attachments
to localDirectory
field.
When I run integration test on Windows machine it fails to bind the path. The exception thrown:
2020-05-26 15:20:59.574 WARN 9024 --- [ Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Could not get URL for resource src/main/webapp/C:/Users/myuser/AppData/Local/Temp//subdir/attachments
java.nio.file.InvalidPathException: Illegal char <:> at index 17: src\main\webapp\C:\Users\myuser\AppData\Local\Temp\subdir\attachments
at sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182) ~[na:1.8.0_222]
at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153) ~[na:1.8.0_222]
at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77) ~[na:1.8.0_222]
at sun.nio.fs.WindowsPath.parse(WindowsPath.java:94) ~[na:1.8.0_222]
at sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:255) ~[na:1.8.0_222]
at java.io.File.toPath(File.java:2234) ~[na:1.8.0_222]
at org.springframework.core.io.FileSystemResource.<init>(FileSystemResource.java:82) ~[spring-core-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.core.io.FileSystemResourceLoader$FileSystemContextResource.<init>(FileSystemResourceLoader.java:65) ~[spring-core-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.core.io.FileSystemResourceLoader.getResourceByPath(FileSystemResourceLoader.java:54) ~[spring-core-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.core.io.DefaultResourceLoader.getResource(DefaultResourceLoader.java:168) ~[spring-core-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.mock.web.MockServletContext.getResource(MockServletContext.java:332) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.boot.test.mock.web.SpringBootMockServletContext.getResource(SpringBootMockServletContext.java:89) [spring-boot-test-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.web.context.support.ServletContextResource.exists(ServletContextResource.java:104) [spring-web-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.propertyeditors.PathEditor.setAsText(PathEditor.java:100) [spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.TypeConverterDelegate.doConvertTextValue(TypeConverterDelegate.java:429) [spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.TypeConverterDelegate.doConvertValue(TypeConverterDelegate.java:402) [spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:155) [spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:73) [spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:45) [spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.boot.context.properties.bind.BindConverter$TypeConverterConverter.convert(BindConverter.java:230) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41) [spring-core-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:191) [spring-core-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.boot.context.properties.bind.BindConverter$CompositeConversionService.convert(BindConverter.java:164) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:96) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:88) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bindProperty(Binder.java:313) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:258) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:214) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$3(Binder.java:322) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:80) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:70) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.lambda$null$4(Binder.java:329) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_222]
at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359) ~[na:1.8.0_222]
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) ~[na:1.8.0_222]
at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499) ~[na:1.8.0_222]
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486) ~[na:1.8.0_222]
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) ~[na:1.8.0_222]
at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152) ~[na:1.8.0_222]
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_222]
at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:531) ~[na:1.8.0_222]
at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$5(Binder.java:330) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:429) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:415) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder$Context.access$400(Binder.java:372) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:328) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:269) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:214) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$3(Binder.java:322) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:80) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:70) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.lambda$null$4(Binder.java:329) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_222]
at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359) ~[na:1.8.0_222]
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) ~[na:1.8.0_222]
at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499) ~[na:1.8.0_222]
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486) ~[na:1.8.0_222]
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) ~[na:1.8.0_222]
at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152) ~[na:1.8.0_222]
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_222]
at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:531) ~[na:1.8.0_222]
at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$5(Binder.java:330) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:429) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:415) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder$Context.access$400(Binder.java:372) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:328) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:269) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:214) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$3(Binder.java:322) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:80) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:70) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.lambda$null$4(Binder.java:329) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_222]
at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359) ~[na:1.8.0_222]
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) ~[na:1.8.0_222]
at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499) ~[na:1.8.0_222]
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486) ~[na:1.8.0_222]
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) ~[na:1.8.0_222]
at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152) ~[na:1.8.0_222]
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_222]
at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:531) ~[na:1.8.0_222]
at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$5(Binder.java:330) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:429) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:415) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder$Context.access$400(Binder.java:372) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:328) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:269) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:214) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:202) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:185) [spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:78) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:101) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:89) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:414) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1770) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1255) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1175) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:857) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:760) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:218) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1341) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1187) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:847) ~[spring-beans-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) ~[spring-context-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) ~[spring-boot-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120) ~[spring-boot-test-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) ~[junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) ~[junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) ~[junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) ~[junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) ~[junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) ~[junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.junit.runners.ParentRunner.run(ParentRunner.java:363) ~[junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) ~[spring-test-5.1.11.RELEASE.jar:5.1.11.RELEASE]
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110) ~[na:na]
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58) ~[na:na]
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38) ~[na:na]
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62) ~[na:na]
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) ~[na:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_222]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_222]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_222]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_222]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na]
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) ~[na:na]
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) ~[na:na]
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source) ~[na:na]
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:118) ~[na:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_222]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_222]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_222]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_222]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na]
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182) ~[na:na]
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164) ~[na:na]
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:412) ~[na:na]
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) ~[na:na]
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48) ~[na:na]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_222]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_222]
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) ~[na:na]
at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_222]
I think the root of the problem is the same as the problem of author of this issue: Spring (PathEditor
or some other component - I did not digging so deep) wrongly assumes that path, that starts with C:\
is not absolute and tries to prepend it with the context path.
Earlier when I run into this problem I solved it adding file:///
at the start of the path. This way PathEditor
tries to resolve it as URI
first, then tries to use ResourceEditor
. But this solution actually doesn't always work. For some reason (which I don't understand) PathEditor
checks the path (resource) for existence before trying to use resolved resource path value. But when I run my tests the target directory can exist or not exist. Both cases are correct. If it exists and my property has file:///
prefix (like: file:///${java.io.tmpdir}/subdir/attachments
), then PathEditor
successfully resolves it to Path
instance. But if the path doesn't exist at the moment, PathEditor
cannot resolve it. And the reason why it tries to resolve the path using ResourceEditor
instead of simple URI
is because the URI file:///C:\Users\myuser\AppData\Local\Temp/subdir/attachments
is not correct because URI can't have \
character. But ResourceEditor
somehow ignores this problem and correctly resolves the path. But then PathEditor
ignores the result just because path doesn't exist at the moment. Very strange.
The last problem is not directly related to absolute path handling of PathEditor
on Windows. It's here more to describe the complexity of the problem: absolute path handling on different OSes, checking for existence etc. All this logic should be reconsidered to make PathEditor
work in all corner cases.
Edit
Actually, I was wrong. The test doesn't fail to start. It just logs the exception on WARN
level, but then PathEditor
checks, that this resource (src/main/webapp/C:/Users/myuser/AppData/Local/Temp//subdir/attachments
) does not exist and executes:
setValue(Paths.get(text).normalize());
And this last line does exactly what it should: maps C:\Users\myuser\AppData\Local\Temp\/subdir/attachments
to C:\Users\myuser\AppData\Local\Temp\subdir\attachments
on Windows. So it works as expected.
So, without file:///
prefix it works. The only "problem" is stacktrace in the log. But issue for this already exist I think (I remember I filed it 1-2 years ago, but can't find it now).
Maybe the problem is also the fact that it tries to find such a resource. The logic that adds src/main/webapp
prefix before absolute Windows URL is incorrect anyway. The resulting path will always be incorrect.
Comment From: bdurrer
I encountered the exception in the log xak2000 has on spring-boot-starter 2.3.5.RELEASE.
With app.myprop = ${java.io.tmpdir}/myfolder
the conversion to java.nio.file.Path
prefixes the path with src/main/webapp
when loaded from a test (stacktrace same as above). However, it works just fine when booting the app normally.
The SpringBootMockServletContext
executes exists(resourceLocation)
checks with different locations. These all fail (even when the path exists) and cause the logged exception.
It then falls back to super.getResourceLocation(path)
, which prefixes the path with resourceBasePath
. This is likely wrong, as it explicitly checks that location first using getResourceBasePathLocation(path);
which in turn just defaults to super.getResourceLocation
.
I think it is an error, but not the one reported by OP.
Comment From: candrews
The combination of https://github.com/spring-projects/spring-framework/pull/26574 and https://github.com/spring-projects/spring-framework/pull/26575 fixes this issue.
Comment From: jhoeller
Closing this issue in favor of #26575.