I just tried to run my server with Java 9 and got next warning:
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective acce
I know of no way to achieve what you are asking for. As you have pointed out, you would need to add command line options (--add-opens
, though, not --illegal-access=deny
) to the JVM launch.
You wrote:
My goal is to avoid the additional instructions for end users. We have many users with our servers installed and that would be a big inconvenience for them.
By the looks of it, your requirements only leave the conclusion that the project is not ready for Java 9. It should honestly report to its users that it takes a little more time to be fully Java 9 compatible. That's totally ok this early after the release.
You can open
packages in module-info.java
or create an open module
.
For Example: Checkout Step 5 and 6 of Migrating Your Project to Jigsaw Step by Step
module shedlock.example {
requires spring.context;
requires spring.jdbc;
requires slf4j.api;
requires shedlock.core;
requires shedlock.spring;
requires HikariCP;
requires shedlock.provider.jdbc.template;
requires java.sql;
opens net.javacrumbs.shedlockexample to spring.core, spring.beans, spring.context;
}
open module shedlock.example {
requires spring.context;
requires spring.jdbc;
requires slf4j.api;
requires shedlock.core;
requires shedlock.spring;
requires HikariCP;
requires shedlock.provider.jdbc.template;
requires java.sql;
}
There are ways to disable illegal access warning, though I do not recommend doing this.
Since the warning is printed to the default error stream, you can simply close this stream and redirect stderr
to stdout
.
public static void disableWarning() {
System.err.close();
System.setErr(System.out);
}
Notes:
System.setErr
, since the reference to error stream is saved in IllegalAccessLogger.warningStream
field early at JVM bootstrap.A good news is that sun.misc.Unsafe
can be still accessed in JDK 9 without warnings. The solution is to reset internal IllegalAccessLogger
with the help of Unsafe API.
public static void disableWarning() {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe u = (Unsafe) theUnsafe.get(null);
Class cls = Class.forName("jdk.internal.module.IllegalAccessLogger");
Field logger = cls.getDeclaredField("logger");
u.putObjectVolatile(cls, u.staticFieldOffset(logger), null);
} catch (Exception e) {
// ignore
}
}
Here is what worked for me
-Djdk.module.illegalAccess=deny
There is another method, not based on any hacks, that was not mentioned in any of the answers above. It works however only for code running on classpath. So any library that needs to support running on Java 9+ could use this technique, as long as it is run from classpath.
It is based on a fact that it is allowed for code running on classpath (i.e. from the unnamed module) to freely dynamically open packages of any module (it can be done only from the target module itself, or from the unnamed module).
For example, given this code, accessing a private field of java.io.Console
class:
Field field = Console.class.getDeclaredField("formatter");
field.setAccessible(true);
In order not to cause the warning, we have to open the target module's package to our module:
if (!ThisClass.class.getModule().isNamed()) {
Console.class.getModule().addOpens(Console.class.getPackageName(), ThisClass.class.getModule());
}
We've also added a check that we're indeed running on classpath.
I've come up with a way to disable that warning without using Unsafe nor accessing any undocumented APIs. It works by using Reflection to set the FilterOutputStream::out
field of System.err
to null.
Of course, attempting to use Reflection will actually throw the warning we're trying to supress, but we can exploit concurrency to work around that:
System.err
so that no other thread can write to it.setAccessible
on the out
field. One of them will hang when trying to show the warning, but the other will complete.out
field of System.err
to null and release the lock on System.err
. The second thread will now complete, but no warning will be displayed.out
field of System.err
.The following code demostrates this:
public void suppressWarning() throws Exception
{
Field f = FilterOutputStream.class.getDeclaredField("out");
Runnable r = () -> { f.setAccessible(true); synchronized(this) { this.notify(); }};
Object errorOutput;
synchronized (this)
{
synchronized (System.err) //lock System.err to delay the warning
{
new Thread(r).start(); //One of these 2 threads will
new Thread(r).start(); //hang, the other will succeed.
this.wait(); //Wait 1st thread to end.
errorOutput = f.get(System.err); //Field is now accessible, set
f.set(System.err, null); // it to null to suppress the warning
} //release System.err to allow 2nd thread to complete.
this.wait(); //Wait 2nd thread to end.
f.set(System.err, errorOutput); //Restore System.err
}
}
This code will work even if --illegal-access
is set to "warn" or "debug", since these modes don't show the warning more than once for the same caller.
Also, instead of restoring the original state of System.err
, you can also set its out
field to a custom OutputStream, so you can filter future warnings.