Jeroen Peschier created IO-895:
----------------------------------
Summary: ExceptionInInitializerError when using Commons IO on the
module path
Key: IO-895
URL: https://issues.apache.org/jira/browse/IO-895
Project: Commons IO
Issue Type: Bug
Affects Versions: 2.22.0
Environment: Java 25, Linux
Reporter: Jeroen Peschier
When migrating a project to Java 25 with modularization, I ran into an issue
where Commons IO 2.22.0 throws an Error when invoking
BufferedFileChannelInputStream::close with Commons IO JAR on the module path
while having "requires org.apache.commons.io;" in the module-info:
{noformat}
Exception in thread "main" java.lang.ExceptionInInitializerError
at
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.cleanDirectBuffer(BufferedFileChannelInputStream.java:230)
at
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.clean(BufferedFileChannelInputStream.java:217)
at
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.close(BufferedFileChannelInputStream.java:240)
... snip private code
Caused by: java.lang.IllegalStateException: Failed to initialize a Cleaner.
at
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:102)
at
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.<clinit>(ByteBufferCleaner.java:79)
... 6 more
Caused by: java.lang.ClassNotFoundException: sun.misc.Cleaner
at
java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:467)
at java.base/java.lang.Class.forName(Class.java:458)
at
[email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:47)
at
[email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:41)
at
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:97)
... 7 more{noformat}
Code that reproduces the above stacktrace:
{code:java}
var input = new BufferedFileChannelInputStream.Builder()
.setPath("some-existing-file")
.get();
input.close();{code}
I noticed that the current HEAD
([f9de7a4|https://github.com/apache/commons-io/commit/f9de7a458b9ff70151994bbbacae3088047b8338])
has an extra setter "setClean" for the builder that aims to skip the cleanup:
{code:java}
var input = new BufferedFileChannelInputStream.Builder()
.setPath("some-existing-file")
.setClean(false)
.get();
input.close();{code}
However running with a snapshot of
([f9de7a4|https://github.com/apache/commons-io/commit/f9de7a458b9ff70151994bbbacae3088047b8338])
and using "setClean(false)" I am seeing the same exception:
{noformat}
Exception in thread "main" java.lang.ExceptionInInitializerError
at
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.cleanBuffer(BufferedFileChannelInputStream.java:252)
at
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.close(BufferedFileChannelInputStream.java:261)
... snip private code
Caused by: java.lang.IllegalStateException: Failed to initialize a direct
ByteBuffer Cleaner.
at
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:116)
at
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.<clinit>(ByteBufferCleaner.java:82)
... 3 more
Caused by: java.lang.ClassNotFoundException: sun.misc.Cleaner
at
java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:467)
at java.base/java.lang.Class.forName(Class.java:458)
at
[email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:50)
at
[email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:44)
at
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:111)
... 4 more{noformat}
So both 2.22.0 and 2.23.0-SNAPSHOT fails on construction of
ByteBufferCleaner.Java8Cleaner when executed from the module path. It never
gets to a Java9+ compatible cleaner because ExceptionInInitializerError (not an
Exception) never triggers the fall-through exception handling in
ByteBufferCleaner::getCleaner.
The Common IO unit tests seem to run on the classpath so they don't highlight
this issue, although there are warnings when running with the classpath as well:
{noformat}
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::invokeCleaner has been called by
org.apache.commons.io.input.ByteBufferCleaner$Java9Cleaner
(file:/REDACTED/.m2/repository/commons-io/commons-io/2.22.0/commons-io-2.22.0.jar)
WARNING: Please consider reporting this to the maintainers of class
org.apache.commons.io.input.ByteBufferCleaner$Java9Cleaner WARNING:
sun.misc.Unsafe::invokeCleaner will be removed in a future release{noformat}
An obvious workaround is to put the Commons IO jar on the classpath. Beside
negating the benefits of Java modules, this is not an (easy) option for
application distributions that no longer have a classpath.
Ideas on how to fix this:
- Maybe ByteBufferCleaner needs a Java8 and Java9+ version in a multi-release
JAR? That way the ExceptionInInitializerError could be prevented altogether.
- Since Java 9, the replacement for sun.misc.Cleaner is
jdk.internal.ref.Cleaner. Still internal to the JDK though.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)