I have an application that uses a third party FileSystemProvider implementation based on S3 packaged in its own JAR file, inside the Spring-Boot executable JAR.

On start of my Spring Boot application from the executable spring-boot jar ( built using standard gradle spring boot plugin) , the 3rd party FileSystemProvider is not found when FileSystems.getFileSystem(...) is called, so FileSystems.newFileSystem(...) is called passing the class loader of (Thread.currentThread().getContextClassLoader()).

All works, and I am able to use the 3rd party FileSystem implementation.

On application startup and bean creation, I have another class that does the same thing , in which the same calls are made. FileSystems.getFileSystem(...) throws FileSystemNotFoundException ( yes..the JDK does not 'install' the newly created FileSystem previously created in its cache) therefore the code calls FileSystems.newFileSystem(...) which now throws an exception, since the 3rd party provider throws an Exception when it tries to create a file system already created. Leaving no way to get a hold of the 3rd party FileSystem implementation, i.e. get...() throws an exception and then new...() now throws an exception on subsequent calls.

I truly believe this is a flaw in the FileSystems class in the JDK, but if the Spring-Boot class loader, LaunchedURLClassLoader would find the 3rd party FileSystem implementation in the executable JAR, then the FileSystems.getFileSystem(...) would not throw an exception and there would be no need to ever call FileSystems.newFileSystem(...)

I have tried to add the 3rd party JAR to /java_home/jre/lib/ext, but there are dependencies that the 3rd party jar needs and those jars would also have to be added to /jre/lib/ext which I wish not to do if possible.

Any thoughts on how to make the LaunchedURLClassLoader find this ServiceProvider implementation of the FileSystemProvider that is in the spring-boot executable jar?

Comment From: wilkinsona

Can you please provide a small sample project that demonstrates the behaviour which you have described above?

Comment From: ulmermark

Hope this helps....

if run as a FAT jar using java -jar build/libs/jarfile

you will see the getFileSystem(...) throw an exception, newFileSystem(..) works and then a getFileSystem(...) fails, since the S3FileSystem is not part of the installed providers according to the class loader using for a Spring Boot application.

If you run the application in an IDE ( eclipse) as a Spring Boot application ( a different class loader is used) and that class loader finds the S3FileSystem on the classpath in the s3...jar file.

Or run by extracting the JAR and using java -classpath .:BOOT-INF/lib/* org.spring....Launcher ( or something like this) you will see that the 2nd getFileSystem(...) works without Exception

spn-rpps-biller-svc.zip

Comment From: wilkinsona

Thanks for the sample.

The javadoc for FileSystems states that:

Installed providers are loaded using the service-provider loading facility defined by the ServiceLoader class. Installed providers are loaded using the system class loader. If the system class loader cannot be found then the extension class loader is used; if there is no extension class loader then the bootstrap class loader is used.

A jar nested in BOOT-INF/lib isn't on the class path of the system class loader so the provider can't be loaded. This is rather limiting, but the designers of the API seem to be aware of this, hence the newFileSystem method that takes a ClassLoader. It's unfortunate that it only works once, but it still appears to be your best option. To avoid the single-use problem, you could wrap the newFileSystem call in something that caches the S3FileSystem.

Comment From: dhpalan

@piyushSKY The issue https://github.com/reactor/reactor-netty/issues/2582 looks similar to the issue described here.