ADDITIONAL SYSTEM INFORMATION :
OS: windows_11
Java Runtime Information:
openjdk version "22.0.1" 2024-04-16
OpenJDK Runtime Environment (build 22.0.1+8-16)
OpenJDK 64-Bit Server VM (build 22.0.1+8-16, mixed mode)
maven info:
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Java version: 22.0.1, vendor: Oracle Corporation, runtime: jdk-22
platform encoding: UTF-8
OS name: "windows 11", version: "10.0", arch: "amd64", family: "windows"
A DESCRIPTION OF THE PROBLEM :
When a custom FileSystemProvider is set by "-Djava.nio.file.spi.DefaultFileSystemProvider=org.example.MyFileSystemProvider" on VM options and the code is running in the custom jrt which is build by jmod and jlink tool the circular dependency between FileSystem and ImageReaderFactory will cause CNFE of the org.example.MyFileSystemProvider.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1.compile and package the source code by maven: `mvn clean package`
2. create jmod by `jmod create --class-path ./demo-1.0-SNAPSHOT.jar demo.jmode`
3. jlink by `jlink --output ./myjre --module-path "demo.jmod;$(JAVA_HOME)/jmods" --add-modules ALL-MODULE-PATH`
4. run `myjre/bin/java "-Djava.nio.file.spi.DefaultFileSystemProvider=org.example.MyFileSystemProvider" -m demo`
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
org.example.MyFileSystemProvider will be set successfully and there is no exception.
ACTUAL -
CNFE:
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.Error: java.lang.ClassNotFoundException: org.example.MyFileSystemProvider
at java.base/java.nio.file.FileSystems$DefaultFileSystemHolder.getDefaultProvider(FileSystems.java:134)
at java.base/java.nio.file.FileSystems$DefaultFileSystemHolder$1.run(FileSystems.java:103)
at java.base/java.nio.file.FileSystems$DefaultFileSystemHolder$1.run(FileSystems.java:101)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:319)
at java.base/java.nio.file.FileSystems$DefaultFileSystemHolder.defaultFileSystem(FileSystems.java:101)
at java.base/java.nio.file.FileSystems$DefaultFileSystemHolder.<clinit>(FileSystems.java:94)
at java.base/java.nio.file.FileSystems.getDefault(FileSystems.java:183)
at java.base/java.nio.file.Path.of(Path.java:148)
at java.base/java.nio.file.Paths.get(Paths.java:69)
at java.base/jdk.internal.jimage.ImageReaderFactory.<clinit>(ImageReaderFactory.java:51)
at java.base/jdk.internal.module.SystemModuleFinders$SystemImage.<clinit>(SystemModuleFinders.java:385)
at java.base/jdk.internal.module.SystemModuleFinders$SystemModuleReader.findImageLocation(SystemModuleFinders.java:430)
at java.base/jdk.internal.module.SystemModuleFinders$SystemModuleReader.read(SystemModuleFinders.java:485)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:814)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassInModuleOrNull(BuiltinClassLoader.java:745)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClass(BuiltinClassLoader.java:622)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:637)
at java.base/java.lang.Class.forName(Class.java:620)
at java.base/java.lang.Class.forName(Class.java:595)
at java.base/sun.launcher.LauncherHelper.loadModuleMainClass(LauncherHelper.java:798)
at java.base/sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:733)
Caused by: java.lang.ClassNotFoundException: org.example.MyFileSystemProvider
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:642)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:529)
at java.base/java.lang.Class.forName(Class.java:508)
at java.base/java.nio.file.FileSystems$DefaultFileSystemHolder.getDefaultProvider(FileSystems.java:124)
... 20 more
---------- BEGIN SOURCE ----------
MyFileSystemProvider.java:
```java
package org.example;
import java.io.IOException;
import java.net.URI;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
import java.util.Map;
import java.util.Set;
public class MyFileSystemProvider extends FileSystemProvider {
private final FileSystemProvider fileSystemProvider;
public MyFileSystemProvider(FileSystemProvider fileSystemProvider) {
this.fileSystemProvider = fileSystemProvider;
}
@Override
public String getScheme() {
return fileSystemProvider.getScheme();
}
@Override
public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
return fileSystemProvider.newFileSystem(uri, env);
}
@Override
public FileSystem getFileSystem(URI uri) {
return fileSystemProvider.getFileSystem(uri);
}
@Override
public Path getPath(URI uri) {
return fileSystemProvider.getPath(uri);
}
@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
return fileSystemProvider.newByteChannel(path, options, attrs);
}
@Override
public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
return fileSystemProvider.newDirectoryStream(dir, filter);
}
@Override
public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
fileSystemProvider.createDirectory(dir, attrs);
}
@Override
public void delete(Path path) throws IOException {
fileSystemProvider.delete(path);
}
@Override
public void copy(Path source, Path target, CopyOption... options) throws IOException {
fileSystemProvider.copy(source, target, options);
}
@Override
public void move(Path source, Path target, CopyOption... options) throws IOException {
fileSystemProvider.move(source, target, options);
}
@Override
public boolean isSameFile(Path path, Path path2) throws IOException {
return fileSystemProvider.isSameFile(path, path2);
}
@Override
public boolean isHidden(Path path) throws IOException {
return fileSystemProvider.isHidden(path);
}
@Override
public FileStore getFileStore(Path path) throws IOException {
return fileSystemProvider.getFileStore(path);
}
@Override
public void checkAccess(Path path, AccessMode... modes) throws IOException {
fileSystemProvider.checkAccess(path, modes);
}
@Override
public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
return fileSystemProvider.getFileAttributeView(path, type, options);
}
@Override
public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
return fileSystemProvider.readAttributes(path, type, options);
}
@Override
public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
return fileSystemProvider.readAttributes(path, attributes, options);
}
@Override
public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
fileSystemProvider.setAttribute(path, attribute, value, options);
}
}
```
Main.java:
```java
package org.example;
public class Main {
public static void main(String[] args){
System.out.println("success");
}
}
```
module-info.java:
```java
module demo {
requires java.base;
exports org.example;
}
```
pom.xml:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>22</source>
<target>22</target>
<encoding>UTF-8</encoding>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<manifest>
<mainClass>org.example.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
```
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
The reason for this bug is the same as [that NPE bug](https://bugs.openjdk.org/browse/JDK-8263940?jql=text%20~%20%22java.nio.file.spi.DefaultFileSystemProvider%22), both caused by circular dependencies during class loading resulting in field values being initialized to NULL. The difference fromJDK-8263940 is that JDK-8263940 directly manifests as NPE, while in this bug, it manifests as CNFE.
In SystemModuleFinders$SystemModuleReader.java
```java
@Override
public Optional<ByteBuffer> read(String name) throws IOException {
// location returned by findImageLocation will be NULL
ImageLocation location = findImageLocation(name);
if (location != null) {
return Optional.of(SystemImage.reader().getResourceBuffer(location));
} else {
return Optional.empty();
}
}
```
In findImageLocation method:
```java
private ImageLocation findImageLocation(String name) throws IOException {
Objects.requireNonNull(name);
if (closed)
throw new IOException("ModuleReader is closed");
// SystemImage will be initialized for the first time when loading the Main class which can be seen in the error stack.And then to initialize ImageReaderFactory.Then pay attention to the `private static final Path BOOT_MODULES_JIMAGE`, this field will call Paths which will also call FileSystem#getDefault.The getDefault method will load the `org.example.MyFileSystemProvider` in which code will run to here again! But now we are still in the process of initializing the `private static final Path BOOT_MODULES_JIMAGE` field. Therefor imageReader will be NULL finally.
ImageReader imageReader = SystemImage.reader();
if (imageReader != null) {
return imageReader.findLocation(module, name);
} else {
// not an images build
return null;
}
}
```
A simple approach would be to follow the same method asJDK-8263940:
In ImageReaderFactory:
change `Paths.get` call to `DefaultFileSystemProvider.theFileSystem().getPath()`:
```java
private static final Path BOOT_MODULES_JIMAGE = DefaultFileSystemProvider.theFileSystem().getPath(JAVA_HOME, "lib", "modules");
```
FREQUENCY : always
OS: windows_11
Java Runtime Information:
openjdk version "22.0.1" 2024-04-16
OpenJDK Runtime Environment (build 22.0.1+8-16)
OpenJDK 64-Bit Server VM (build 22.0.1+8-16, mixed mode)
maven info:
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Java version: 22.0.1, vendor: Oracle Corporation, runtime: jdk-22
platform encoding: UTF-8
OS name: "windows 11", version: "10.0", arch: "amd64", family: "windows"
A DESCRIPTION OF THE PROBLEM :
When a custom FileSystemProvider is set by "-Djava.nio.file.spi.DefaultFileSystemProvider=org.example.MyFileSystemProvider" on VM options and the code is running in the custom jrt which is build by jmod and jlink tool the circular dependency between FileSystem and ImageReaderFactory will cause CNFE of the org.example.MyFileSystemProvider.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1.compile and package the source code by maven: `mvn clean package`
2. create jmod by `jmod create --class-path ./demo-1.0-SNAPSHOT.jar demo.jmode`
3. jlink by `jlink --output ./myjre --module-path "demo.jmod;$(JAVA_HOME)/jmods" --add-modules ALL-MODULE-PATH`
4. run `myjre/bin/java "-Djava.nio.file.spi.DefaultFileSystemProvider=org.example.MyFileSystemProvider" -m demo`
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
org.example.MyFileSystemProvider will be set successfully and there is no exception.
ACTUAL -
CNFE:
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.Error: java.lang.ClassNotFoundException: org.example.MyFileSystemProvider
at java.base/java.nio.file.FileSystems$DefaultFileSystemHolder.getDefaultProvider(FileSystems.java:134)
at java.base/java.nio.file.FileSystems$DefaultFileSystemHolder$1.run(FileSystems.java:103)
at java.base/java.nio.file.FileSystems$DefaultFileSystemHolder$1.run(FileSystems.java:101)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:319)
at java.base/java.nio.file.FileSystems$DefaultFileSystemHolder.defaultFileSystem(FileSystems.java:101)
at java.base/java.nio.file.FileSystems$DefaultFileSystemHolder.<clinit>(FileSystems.java:94)
at java.base/java.nio.file.FileSystems.getDefault(FileSystems.java:183)
at java.base/java.nio.file.Path.of(Path.java:148)
at java.base/java.nio.file.Paths.get(Paths.java:69)
at java.base/jdk.internal.jimage.ImageReaderFactory.<clinit>(ImageReaderFactory.java:51)
at java.base/jdk.internal.module.SystemModuleFinders$SystemImage.<clinit>(SystemModuleFinders.java:385)
at java.base/jdk.internal.module.SystemModuleFinders$SystemModuleReader.findImageLocation(SystemModuleFinders.java:430)
at java.base/jdk.internal.module.SystemModuleFinders$SystemModuleReader.read(SystemModuleFinders.java:485)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:814)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassInModuleOrNull(BuiltinClassLoader.java:745)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClass(BuiltinClassLoader.java:622)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:637)
at java.base/java.lang.Class.forName(Class.java:620)
at java.base/java.lang.Class.forName(Class.java:595)
at java.base/sun.launcher.LauncherHelper.loadModuleMainClass(LauncherHelper.java:798)
at java.base/sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:733)
Caused by: java.lang.ClassNotFoundException: org.example.MyFileSystemProvider
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:642)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:529)
at java.base/java.lang.Class.forName(Class.java:508)
at java.base/java.nio.file.FileSystems$DefaultFileSystemHolder.getDefaultProvider(FileSystems.java:124)
... 20 more
---------- BEGIN SOURCE ----------
MyFileSystemProvider.java:
```java
package org.example;
import java.io.IOException;
import java.net.URI;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
import java.util.Map;
import java.util.Set;
public class MyFileSystemProvider extends FileSystemProvider {
private final FileSystemProvider fileSystemProvider;
public MyFileSystemProvider(FileSystemProvider fileSystemProvider) {
this.fileSystemProvider = fileSystemProvider;
}
@Override
public String getScheme() {
return fileSystemProvider.getScheme();
}
@Override
public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
return fileSystemProvider.newFileSystem(uri, env);
}
@Override
public FileSystem getFileSystem(URI uri) {
return fileSystemProvider.getFileSystem(uri);
}
@Override
public Path getPath(URI uri) {
return fileSystemProvider.getPath(uri);
}
@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
return fileSystemProvider.newByteChannel(path, options, attrs);
}
@Override
public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
return fileSystemProvider.newDirectoryStream(dir, filter);
}
@Override
public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
fileSystemProvider.createDirectory(dir, attrs);
}
@Override
public void delete(Path path) throws IOException {
fileSystemProvider.delete(path);
}
@Override
public void copy(Path source, Path target, CopyOption... options) throws IOException {
fileSystemProvider.copy(source, target, options);
}
@Override
public void move(Path source, Path target, CopyOption... options) throws IOException {
fileSystemProvider.move(source, target, options);
}
@Override
public boolean isSameFile(Path path, Path path2) throws IOException {
return fileSystemProvider.isSameFile(path, path2);
}
@Override
public boolean isHidden(Path path) throws IOException {
return fileSystemProvider.isHidden(path);
}
@Override
public FileStore getFileStore(Path path) throws IOException {
return fileSystemProvider.getFileStore(path);
}
@Override
public void checkAccess(Path path, AccessMode... modes) throws IOException {
fileSystemProvider.checkAccess(path, modes);
}
@Override
public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
return fileSystemProvider.getFileAttributeView(path, type, options);
}
@Override
public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
return fileSystemProvider.readAttributes(path, type, options);
}
@Override
public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
return fileSystemProvider.readAttributes(path, attributes, options);
}
@Override
public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
fileSystemProvider.setAttribute(path, attribute, value, options);
}
}
```
Main.java:
```java
package org.example;
public class Main {
public static void main(String[] args){
System.out.println("success");
}
}
```
module-info.java:
```java
module demo {
requires java.base;
exports org.example;
}
```
pom.xml:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>22</source>
<target>22</target>
<encoding>UTF-8</encoding>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<manifest>
<mainClass>org.example.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
```
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
The reason for this bug is the same as [that NPE bug](https://bugs.openjdk.org/browse/JDK-8263940?jql=text%20~%20%22java.nio.file.spi.DefaultFileSystemProvider%22), both caused by circular dependencies during class loading resulting in field values being initialized to NULL. The difference from
In SystemModuleFinders$SystemModuleReader.java
```java
@Override
public Optional<ByteBuffer> read(String name) throws IOException {
// location returned by findImageLocation will be NULL
ImageLocation location = findImageLocation(name);
if (location != null) {
return Optional.of(SystemImage.reader().getResourceBuffer(location));
} else {
return Optional.empty();
}
}
```
In findImageLocation method:
```java
private ImageLocation findImageLocation(String name) throws IOException {
Objects.requireNonNull(name);
if (closed)
throw new IOException("ModuleReader is closed");
// SystemImage will be initialized for the first time when loading the Main class which can be seen in the error stack.And then to initialize ImageReaderFactory.Then pay attention to the `private static final Path BOOT_MODULES_JIMAGE`, this field will call Paths which will also call FileSystem#getDefault.The getDefault method will load the `org.example.MyFileSystemProvider` in which code will run to here again! But now we are still in the process of initializing the `private static final Path BOOT_MODULES_JIMAGE` field. Therefor imageReader will be NULL finally.
ImageReader imageReader = SystemImage.reader();
if (imageReader != null) {
return imageReader.findLocation(module, name);
} else {
// not an images build
return null;
}
}
```
A simple approach would be to follow the same method as
In ImageReaderFactory:
change `Paths.get` call to `DefaultFileSystemProvider.theFileSystem().getPath()`:
```java
private static final Path BOOT_MODULES_JIMAGE = DefaultFileSystemProvider.theFileSystem().getPath(JAVA_HOME, "lib", "modules");
```
FREQUENCY : always