20

I have cglib as a transitive dependency in a Maven project. Despite adding what I believe to be the correct --add-opens I can't get the library to work with Java 16.

How do I get cglib to work with Java 16? I raised this issue on the github page.

Minimal reproducible example:

Main.java

import net.sf.cglib.proxy.Enhancer;

public class Main {
    public static void main(String[] args) {
        new Enhancer();
    }
}

With Java 15:

javac -cp cglib-3.3.0.jar Main.java

java --add-opens java.base/java.lang=ALL-UNNAMED -cp cglib-3.3.0.jar:asm-7.1.jar:. Main

Picked up _JAVA_OPTIONS: -Duser.language=en -Duser.country=GB
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by net.sf.cglib.core.ReflectUtils$1 (file:/Users/rbain/Desktop/cglib-3.3.0.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of net.sf.cglib.core.ReflectUtils$1
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

With Java 16:

javac -cp cglib-3.3.0.jar Main.java

java --add-opens java.base/java.lang=ALL-UNNAMED -cp cglib-3.3.0.jar:asm-7.1.jar:. Main

Exception in thread "main" java.lang.ExceptionInInitializerError
    at Main.main(Main.java:5)
Caused by: net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @11739fa6
    at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:464)
    at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:339)
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:96)
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:94)
    at net.sf.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
    at net.sf.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:119)
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)
    at net.sf.cglib.core.KeyFactory$Generator.create(KeyFactory.java:221)
    at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:174)
    at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:153)
    at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:73)
    ... 1 more
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @11739fa6
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:357)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
    at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
    at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
    at net.sf.cglib.core.ReflectUtils$1.run(ReflectUtils.java:61)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:554)
    at net.sf.cglib.core.ReflectUtils.<clinit>(ReflectUtils.java:52)
    at net.sf.cglib.core.KeyFactory$Generator.generateClass(KeyFactory.java:243)
    at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
    at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:332)
    ... 13 more
Naman
  • 27,789
  • 26
  • 218
  • 353
Robert Bain
  • 9,113
  • 8
  • 44
  • 63
  • 1
    do you understand what Rafael said with "you can change the setting that forbids accessing internal API"? I do not, so wondering – Eugene Apr 06 '21 at 20:17
  • @Eugene I wasn't certain but I thought he meant add the `--add-opens`. – Robert Bain Apr 06 '21 at 20:18
  • 1
    that does not look like a "forbid" though. may be he is talking about some certain internal cglib flag? – Eugene Apr 06 '21 at 20:19
  • 3
    Rafael surely meant `--illegal-access=…`. Prior to JDK 16, its default was `--illegal-access=permit`, now it’s `--illegal-access=deny`. You may override it and gain some time until [JEP 403](https://openjdk.java.net/jeps/403) gets implemented and the whole thing will break again. – Holger Apr 08 '21 at 11:06
  • @Holger ah! Excellent point, easier then I expected – Eugene Apr 08 '21 at 11:46
  • @Holger `--illegal-access=permit` resolves the issue as you say. Happy to accept your answer would you care to write one. – Robert Bain Apr 08 '21 at 14:56
  • 4
    The long-run solution is to get an update of `cglib` that doesn't use internal API points in this way, since relatively soon, the workarounds will stop working. You need to report this problem to the `cglib` maintainers; this is their problem to fix. – Brian Goetz Jun 29 '21 at 18:32

1 Answers1

29

Since JDK 16, the default for the --illegal-access option is deny, so “deep reflection” to JDK classes fails.

You can override the behavior by specifying --illegal-access=permit, but you have to be aware of JEP 403: Strongly Encapsulate JDK Internals which is about closing that possibility in a future version, so this option is only a temporary solution.

The permanent solution is to update cglib to a compatible version if/once it exists. The attempt to access ClassLoader.defineClass suggests that the library wants to add classes to a particular context, which can be done via MethodHandles.lookup().defineClass instead (since Java 9). So the code only has to switch to the new way of adding classes.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • 3
    fyi, took the liberty to reference this back to the GitHub issue linked in the question for future audience – Naman Apr 08 '21 at 17:41
  • 2
    From JDK17 on, setting `--illegal-access=permit` or `warn` doesn't work any longer. it is still possible to build a workaround by manually specifying "add-opens" for accessed packages in the manifest or as jvmArg (e.g. `--add-opens=java.base/java.util=ALL-UNNAMED`) – Florian Koch Feb 07 '22 at 12:31
  • 3
    @FlorianKoch well, yes, ⅔ of this answer are about the expectation of this to happen. The real fix is described as well. Using `--add-opens` can also only be a temporary work-around and requires the user to know precisely a) the module/package to open and b) to which target module it needs to be opened. – Holger Feb 07 '22 at 13:11
  • totally true, however in my case I'm using a dependency (that I can't get rid of or update) which is causing the problems, so there is no way to fix the root of the problem – Florian Koch Feb 09 '22 at 12:39
  • 3
    @FlorianKoch then, there’s no way to fix the problem *on your side*. But there should be maintainers already working on the problem at the other side. If not, it’s time to think about long term migration… – Holger Feb 09 '22 at 12:45
  • Hello, I've got a similar problem but with java 1.8, and mock ejb, could you I be running with the same issue? Here's a link to my question: https://stackoverflow.com/questions/74465839/i-get-java-lang-exceptionininitializererror-when-trying-to-initializecontext-in – gerumato Nov 16 '22 at 19:22
  • 1
    @gerumato letting aside that this can’t be java 1.8, it’s precisely the same issue. Maybe, you mean java 18? – Holger Nov 16 '22 at 19:33