Skip to content

[IO-895] ExceptionInInitializerError when using Commons IO on the module path - #897

Open
xazap wants to merge 4 commits into
apache:masterfrom
xazap:io-895-ExceptionInInitializerError-when-using-Commons-IO-on-the-module-path
Open

xazap wants to merge 4 commits into
apache:masterfrom
xazap:io-895-ExceptionInInitializerError-when-using-Commons-IO-on-the-module-path

Conversation

@xazap

@xazap xazap commented Oct 20, 2026

Copy link
Copy Markdown

This PR seeks to fix the bug described in IO-895.

Changes made in org.apache.commons.io.input.ByteBufferCleaner

  • Removed risk of ExceptionInInitializerError due to "sun.misc.Unsafe" not loadable when using the module path.
  • Introduced "jdk.internal.ref.Cleaner" as a JDK9+ alternative for "sun.misc.Cleaner".
  • If static member "CLEANER" cannot be initialized, an ExceptionInInitializerError can (theoretically) still occur, but
    should an ExceptionInInitializerError occur, it now provides the correct root cause whereas the previous implementation always provided the stacktrace of the Java 8 failure.

Testing

  • No new test were introduced, all existing tests pass in Java 11 and Java 25.
  • I ran the tests with Java 8 as well, but got 2 failures out of 6621 tests. Both failures are in FileUtilsTest::testIsFileNewerOlder and don't seem to relate to my changes. It looks like a flaky test that has the potential to create different (timestamp) outcomes depending what combination of JVM version/vendor, operating system, file system and physical disks are used.
  • The surefire-plugin configuration was changed to allow module access to "sun.nio.ch" and "jdk.internal.ref" packages when running the build on Java 9+ using the already present build profiles.

Runtime considerations when using Commons IO on the module path

  • By default the "jdk.internal.ref" and "sun.nio.ch" are packages are not accessible to the commons.io module. As such, the following needs to be added to the VM arguments to use Commons IO on the module path: "--add-opens=java.base/sun.nio.ch=org.apache.commons.io --add-opens=java.base/jdk.internal.ref=org.apache.commons.io"

What could be better:

  • ByteBufferCleaner uses Class.forName to dynamically figure out if we are running on Java 8 or 9+. This could be solved more elegantly with a multi-release JAR with separate Java 8 and 9 versions of ByteBufferCleaner. The separate versions would not need Class.forName then.
  • Ideally the unit tests should have a full pass with the classes on the classpath and another full pass with the classes on the module path. The current build does classpath only.

Why this should be merged:

  • Version 2.22.0 and the HEAD (f9de7a4) cannot use the ByteBufferCleaner with the Commons IO JAR on the module path. This PR fixes that.

@agammann agammann left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Java 9+ path now invokes sun.nio.ch.DirectBuffer.cleaner() and jdk.internal.ref.Cleaner.clean() reflectively. Both packages need the --add-opens flags added to the Maven test profile, so those tests do not exercise a normal classpath consumer with no extra JVM arguments. In that case, wouldn’t clean(..., true) fail with IllegalAccessException at invocation? The previous Java9Cleaner used Unsafe.invokeCleaner for that case. Could you retain a no-flags path and add a subprocess test without --add-opens? This would check that the module-path fix does not regress the default classpath use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants