97

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 access by io.netty.util.internal.ReflectionUtil (file:/home/azureuser/server-0.28.0-SNAPSHOT.jar) to constructor java.nio.DirectByteBuffer(long,int)
WARNING: Please consider reporting this to the maintainers of io.netty.util.internal.ReflectionUtil
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

I would like to hide this warning without adding --illegal-access=deny to JVM options during start. Something like:

System.setProperty("illegal-access", "deny");

Is there any way to do that?

All related answers suggesting to use JVM options, I would like to turn off this from code. Is that possible?

To clarify - my question is about turning this warning from the code and not via JVM arguments/flags as stated in similar questions.

Nikita Koksharov
  • 10,283
  • 1
  • 62
  • 71
Dmitriy Dumanskiy
  • 11,657
  • 9
  • 37
  • 57
  • I know that way and the issue is already reported. However, I would like to turn off this warning right now. As the fix for above issue will take some time. – Dmitriy Dumanskiy Sep 27 '17 at 18:53
  • 1
    Once more - I know I can use JVM flags, however, I need to do that via code. Am I not clear here? – Dmitriy Dumanskiy Sep 27 '17 at 18:55
  • 3
    The [`HotSpotDiagnosticMXBean`](https://docs.oracle.com/javase/9/docs/api/com/sun/management/HotSpotDiagnosticMXBean.html#setVMOption-java.lang.String-java.lang.String-) allows changing some JVM options. Not sure if you can use it for this one, and if you could, it's a bit iffy to do that in production. – Mick Mnemonic Sep 27 '17 at 19:04
  • 1
    @nullpointer No. 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. – Dmitriy Dumanskiy Sep 27 '17 at 19:05

9 Answers9

74

There are ways to disable illegal access warning, though I do not recommend doing this.

1. Simple approach

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:

  • This approach merges error and output streams. That may not be desirable in some cases.
  • You cannot redirect warning message just by calling System.setErr, since the reference to error stream is saved in IllegalAccessLogger.warningStream field early at JVM bootstrap.

2. Complicated approach without changing stderr

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
    }
}
apangin
  • 92,924
  • 10
  • 193
  • 247
  • 1
    `jdk.internal.module` is not exported by `java.base` though. You might want to update its source as well. – Naman Sep 28 '17 at 03:04
  • 2
    @nullpointer It is not exported, but it doesn't need to be for the trick to work. – apangin Sep 28 '17 at 07:34
  • @apangin thanks! It is actually working. Nice temp workaround for now (the second one, with hiding logger). – Dmitriy Dumanskiy Sep 28 '17 at 08:09
  • 17
    It's a hack that will likely break in future updates. The right thing to do with issues like this is to submit a bug to the offending library (you may have already done then). You can workaround it temporarily by precisely opening the package with the classes that are being hacked. There are several ways to pass the `--add-opens` option (CLI, JAR manifest of executable JARs, env variables). – Alan Bateman Sep 28 '17 at 08:24
  • 9
    WTF?! Harmless Reflection, e.g. to documented `protected` members of the official API, get a warning, but accessing `sun.misc.Unsafe` does not? Ok, got the message, stop using Reflection (besides for getting `theUnsafe`) and always use `Unsafe`. Then, we even don’t need to turn off the warning… – Holger Sep 28 '17 at 08:32
  • 3
    @Holger Yes, the irony of the situation is that all this fuss around "Encapsulation of internal APIs" might lead to even wider use of Unsafe. – apangin Sep 28 '17 at 12:35
  • 1
    Where should we write this code in order to hide warnings? I mean in which file? – Pratik Patil Apr 19 '18 at 10:54
  • @PratikPatil Create a new class with the above method and call the method once at the application startup. – apangin Apr 19 '18 at 11:47
  • The hack can fail with `UnsatisfiedLinkError: jdk.internal.misc.Unsafe.registerNatives()V` if it's called from system classloader due to special handling in `nativeLookup.cpp:lookup_special_native`. Safer would be to use bootstrap classloader for `Class.forName("jdk.internal.module.IllegalAccessLogger")` – Murka Sep 25 '18 at 13:33
  • 5
    I found another way, you can perform the equivalent to `--add-opens` programmatically, e.g. `Target.class.getModule().addOpens(Target.class.getPackage(), MyClass.class.getModule());` You have to repeat this for every package you want to access. – Holger Jun 12 '20 at 07:48
  • 1
    That ought to be Target. class. getPackage().getName(). – Linus Fernandes Jul 13 '20 at 00:25
  • Actually, `getPackageName()`. – Johannes Kuhn Dec 23 '20 at 09:43
  • the hack worked till... OpenJDK 17.0.1 then breaks with `java.lang.ClassNotFoundException: jdk.internal.module.IllegalAccessLogger` – Hartmut Pfarr Dec 19 '21 at 17:15
  • 1
    @HartmutPfarr well, since the option `--illegal-access=warn` does not exist in JDK 17 anymore, there is no warning and hence, no need for a workaround to hide the warning. – Holger May 26 '23 at 15:13
16

There is another option that does not come with any need for stream suppression and that does not rely on undocumented or unsupported APIs. Using a Java agent, it is possible to redefine modules to export/open the required packages. The code for this would look something like this:

void exportAndOpen(Instrumentation instrumentation) {
  Set<Module> unnamed = 
    Collections.singleton(ClassLoader.getSystemClassLoader().getUnnamedModule());
  ModuleLayer.boot().modules().forEach(module -> instrumentation.redefineModule(
        module,
        unnamed,
        module.getPackages().stream().collect(Collectors.toMap(
          Function.identity(),
          pkg -> unnamed
        )),
        module.getPackages().stream().collect(Collectors.toMap(
           Function.identity(),
           pkg -> unnamed
         )),
         Collections.emptySet(),
         Collections.emptyMap()
  ));
}

You can now run any illegal access without the warning as your application is contained in the unnamed module as for example:

Method method = ClassLoader.class.getDeclaredMethod("defineClass", 
    byte[].class, int.class, int.class);
method.setAccessible(true);

In order to get hold of the Instrumentation instance, you can either write a Java agent what is quite simple and specify it on the command line (rather than the classpath) using -javaagent:myjar.jar. The agent would only contain an premain method as follows:

public class MyAgent {
  public static void main(String arg, Instrumentation inst) {
    exportAndOpen(inst);
  }
}

Alternatively, you can attach dynamically using the attach API which is made accessible conveniently by the byte-buddy-agent project (which I authored):

exportAndOpen(ByteBuddyAgent.install());

which you would need to call prior to the illegal access. Note that this is only available on JDKs and on Linux VM whereas you would need to supply the Byte Buddy agent on the command line as a Java agent if you needed it on other VMs. This can be convenient when you want the self-attachment on test and development machines where JDKs are typically installed.

As others pointed out, this should only serve as an intermediate solution but I fully understand that the current behavior often breaks logging crawlers and console apps which is why I have used this myself in production environments as a short-term solution to using Java 9 and so long I did not encounter any problems.

The good thing, however, is that this solution is robust towards future updates as any operation, even the dynamic attachment is legal. Using a helper process, Byte Buddy even works around the normally forbidden self-attachment.

Farzad Karimi
  • 770
  • 1
  • 12
  • 31
Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • -1 Solutions like this, beyond just being complicated to put in practice, fly in the face of the "strong encapsulation" goals of Java 9's new module system. Instead of promoting such dirty hacks, we should either work with Oracle's JDK developers to solve real-world backward compatibility problems, or work with the users of our tools so they accept the more stringent requirements of JDK 9+. For example, we could ask Oracle to add a (very reasonable) `java.lang.instrument.InstrumentationFactory` class so dynamic attach would no longer be needed. – Rogério Oct 07 '17 at 15:57
  • 11
    This discussion was taking place many months aon the mailinglist and got rejected. If you need to bring a system running on Java 9 and you have a limited budget, sometimes you need to go for an intermediate solution. As it does not use unofficial APIs this neither qualifies as a hack. – Rafael Winterhalter Oct 07 '17 at 17:44
  • @Rogério You're pretty wrong. The problems have to be solved one day, but there's nothing an ordinary developer could or should do. The illegal access happens usually in libraries, which work well now and which probably already have taken care of this: They do the illegal access for efficiency reasons and *fall back* to legal ways when it fails. They won't get updated in this respect as long as there's a non-negligible chance of the illegal access working, i.e., maybe for a few decades. Unless they get a legal efficient way.... – maaartinus Apr 14 '20 at 11:27
  • @Rogério What if I very much _wish_ to "fly in the face of the "strong encapsulation" goals of Java 9's new module system"? I didn't ask for the module system; and blue collar programmer Joe Java certainly didn't ask for it. It affords me many, many headaches and not a single benefit. – barneypitt Jul 22 '22 at 07:59
14
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main {
    @SuppressWarnings("unchecked")
    public static void disableAccessWarnings() {
        try {
            Class unsafeClass = Class.forName("sun.misc.Unsafe");
            Field field = unsafeClass.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Object unsafe = field.get(null);

            Method putObjectVolatile = unsafeClass.getDeclaredMethod("putObjectVolatile", Object.class, long.class, Object.class);
            Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class);

            Class loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
            Field loggerField = loggerClass.getDeclaredField("logger");
            Long offset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
            putObjectVolatile.invoke(unsafe, loggerClass, offset, null);
        } catch (Exception ignored) {
        }
    }

    public static void main(String[] args) {
        disableAccessWarnings();
    }
}

It works for me in JAVA 11.

Gan
  • 188
  • 2
  • 7
  • 1
    @AlexByrth Find a way to set the `logger` field of `jdk.internal.module.IllegalAccessLogger` to null. Take a look at the source code of IllegalAccessLogger. – Gan Oct 10 '19 at 01:53
12

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.

Nicolai Parlog
  • 47,972
  • 24
  • 125
  • 255
6

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.

Ivan
  • 1,552
  • 1
  • 16
  • 25
  • This was helpful, I needed something similar to silence warnings when reflectively accessing `java.net.InetAddress.cache` – neu242 Mar 18 '21 at 18:24
5

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:

  1. Lock System.err so that no other thread can write to it.
  2. Spawn 2 threads that call setAccessible on the out field. One of them will hang when trying to show the warning, but the other will complete.
  3. Set the 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.
  4. Wait for the second thread to end and restore the 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.

Leandro
  • 71
  • 2
  • 3
2

In case anybody would like to redirect the log messages instead of discarding them, this works for me in Java 11. It replaces the stream the illegal access logger writes to.

public class AccessWarnings {

  public static void redirectToStdOut() {
    try {

      // get Unsafe
      Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
      Field field = unsafeClass.getDeclaredField("theUnsafe");
      field.setAccessible(true);
      Object unsafe = field.get(null);

      // get Unsafe's methods
      Method getObjectVolatile = unsafeClass.getDeclaredMethod("getObjectVolatile", Object.class, long.class);
      Method putObject = unsafeClass.getDeclaredMethod("putObject", Object.class, long.class, Object.class);
      Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class);
      Method objectFieldOffset = unsafeClass.getDeclaredMethod("objectFieldOffset", Field.class);

      // get information about the global logger instance and warningStream fields 
      Class<?> loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
      Field loggerField = loggerClass.getDeclaredField("logger");
      Field warningStreamField = loggerClass.getDeclaredField("warningStream");

      Long loggerOffset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
      Long warningStreamOffset = (Long) objectFieldOffset.invoke(unsafe, warningStreamField);

      // get the global logger instance
      Object theLogger = getObjectVolatile.invoke(unsafe, loggerClass, loggerOffset);
      // replace the warningStream with System.out
      putObject.invoke(unsafe, theLogger, warningStreamOffset, System.out);
    } catch (Throwable ignored) {
    }
  }
}

Slawomir Chodnicki
  • 1,525
  • 11
  • 16
1

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;
}
TheKojuEffect
  • 20,103
  • 19
  • 89
  • 125
1

Here is what worked for me

-Djdk.module.illegalAccess=deny
Ich
  • 1,350
  • 16
  • 27