Skip to content

ClassPathResource#getInputStream does not use system class loader as a fallback to locate resources when instancied from ClassPathResource(String) #35223

@FBibonne

Description

@FBibonne

During acceptance of a batch using Spring Framework 6.2.9, while the batch is loading a resource file from classpath with ClassPathResource, this error occurs sometimes :

java.io.FileNotFoundException: class path resource [[mailsTemplates/myTemplate.html] cannot be opened because it does not exist
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:215)
at org.springframework.core.io.Resource.getContentAsString(Resource.java:164)

The error does not occur during functional tests. In production, the resource file is inside the application jar (nested into spring boot executable jar) at the good path. Since I did not provide a class loader to my ClassPathResource, I guess it is a class loader issue : if the constructor ClassPathResource#ClassPathResource(java.lang.String) is not executed with the "good class loader" in the thread context, the error can happen.

I fixed my error providing a "good class loader" at the constructor for the ClassPathResource instance.

I had a look to the method which causes the error : the FileNotFoundException is raised because in the method ClassPathResource#getInputStream when the InputStream returned by this.classLoader#getResourceAsStream is null : :

public InputStream getInputStream() throws IOException {
	InputStream is;
	if (this.clazz != null) {
		is = this.clazz.getResourceAsStream(this.path);
	}
	else if (this.classLoader != null) {
		is = this.classLoader.getResourceAsStream(this.absolutePath);
	}
	else {
		is = ClassLoader.getSystemResourceAsStream(this.absolutePath);
	}
	if (is == null) {
		throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
	}
	return is;
}

The resource location could be resolved by the system class loader if it was called. But the statement is = ClassLoader.getSystemResourceAsStream(this.absolutePath) can only be called if the ClassPathResource instance is created by new ClassPathResource(path, (Class<?>) null). It is not called when the ClassPathResource instance is created by new ClassPathResource(path). So if it has not impact elsewhere, I suggest that when the ClassPathResource object is instancied from ClassPathResource(String) and when, in getInputStream method, InputStream got from this.classLoader.getResourceAsStream(this.absolutePath) is null, the InputStream resolution should fallback to the system class loader. Note that to be consistent, the suggestion should also apply to ClassPathResource#resolveURL.

  • To reproduce my initial bug : run the test org.springframework.core.io.ClassPathResourceTests#testResourceLocation from my fork : the test fails with the FileNotFoundException
    • if you replace var classpathResource = new ClassPathResource("scanned-resources/resource#test1.txt") with var classpathResource = new ClassPathResource("scanned-resources/resource#test1.txt", (Class<?>) null) in the test, it succeeds
    • if you apply the suggestion to ClassPathResource#getInputStream (fallback to system class loader when this.classLoader.getResourceAsStream returns null), the test succeeds also.

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)status: declinedA suggestion or change that we don't feel we should currently apply

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions