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)

Reply via email to