82

This exception occurs in a wide variety of scenarios when running an application on Java 9. Certain libraries and frameworks (Spring, Hibernate, JAXB) are particularly prone to it. Here's an example from Javassist:

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 @1941a8ff
    at java.base/jdk.internal.reflect.Reflection.throwInaccessibleObjectException(Reflection.java:427)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:201)
    at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:192)
    at java.base/java.lang.reflect.Method.setAccessible(Method.java:186)
    at javassist.util.proxy.SecurityActions.setAccessible(SecurityActions.java:102)
    at javassist.util.proxy.FactoryHelper.toClass2(FactoryHelper.java:180)
    at javassist.util.proxy.FactoryHelper.toClass(FactoryHelper.java:163)
    at javassist.util.proxy.ProxyFactory.createClass3(ProxyFactory.java:501)
    at javassist.util.proxy.ProxyFactory.createClass2(ProxyFactory.java:486)
    at javassist.util.proxy.ProxyFactory.createClass1(ProxyFactory.java:422)
    at javassist.util.proxy.ProxyFactory.createClass(ProxyFactory.java:394)

The message says:

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 @1941a8ff

What can be done to avoid the exception and have the program run successfully?

Naman
  • 23,555
  • 22
  • 173
  • 290
Nicolai Parlog
  • 36,673
  • 16
  • 109
  • 236

4 Answers4

116

The exception is caused by the Java Platform Module System that was introduced in Java 9, particularly its implementation of strong encapsulation. It only allows access under certain conditions, the most prominent ones are:

  • the type has to be public
  • the owning package has to be exported

The same limitations are true for reflection, which the code causing the exception tried to use. More precisely the exception is caused by a call to setAccessible. This can be seen in the stack trace above, where the corresponding lines in javassist.util.proxy.SecurityActions look as follows:

static void setAccessible(final AccessibleObject ao,
                          final boolean accessible) {
    if (System.getSecurityManager() == null)
        ao.setAccessible(accessible); // <~ Dragons
    else {
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                ao.setAccessible(accessible);  // <~ moar Dragons
                return null;
            }
        });
    }
}

To make sure the program runs successfully the module system must be convinced to allow access to the element on which setAccessible was called. All information required for that is contained in the exception message but there are a number of mechanisms to achieve this. Which is the best one depends on the exact scenario that caused it.

Unable to make {member} accessible: module {A} does not 'opens {package}' to {B}

By far the most prominent scenarios are the following two:

  1. A library or framework uses reflection to call into a JDK module. In this scenario:

    • {A} is a Java module (prefixed with java. or jdk.)
    • {member} and {package} are parts of the Java API
    • {B} is a library, framework, or application module; often unnamed module @...
  2. A reflection-based library/framework like Spring, Hibernate, JAXB, ... reflects over application code to access beans, entities,... In this scenario:

    • {A} is an application module
    • {member} and {package} are part of the application code
    • {B} is either a framework module or unnamed module @...

Note that some libraries (JAXB, for example) can fail on both accounts so have a close look at what scenario you're in! The one in the question is case 1.

1. Reflective Call Into JDK

The JDK modules are immutable for application developers so we can not change their properties. This leaves only one possible solution: command line flags. With them it is possible to open specific packages up for reflection.

So in a case like above (shortened)...

Unable to make java.lang.ClassLoader.defineClass accessible: module java.base does not "opens java.lang" to unnamed module @1941a8ff

... the correct fix is to launch the JVM as follows:

# --add-opens has the following syntax: {A}/{package}={B}
java --add-opens java.base/java.lang=ALL-UNNAMED

If the reflecting code is in a named module, ALL-UNNAMED can be replaced by its name.

Note that it can sometimes be hard to find a way to apply this flag to the JVM that will actually execute the reflecting code. This can be particularly tough if the code in question is part of the project's build process and is executed in a JVM that the build tool spawned.

If there are too many flags to be added, you might consider using the encapsulation kill switch --permit-illegal-access instead. It will allow all code on the class path to reflect overall named modules. Note that this flag will only work in Java 9!

2. Reflection Over Application Code

In this scenario, it is likely that you can edit the module that reflection is used to break into. (If not, you're effectively in case 1.) That means that command-line flags are not necessary and instead module {A}'s descriptor can be used to open up its internals. There are a variety of choices:

  • export the package with exports {package}, which makes it available at compile and run time to all code
  • export the package to the accessing module with exports {package} to {B}, which makes it available at compile and run time but only to {B}
  • open the package with opens {package}, which makes it available at run time (with or without reflection) to all code
  • open the package to the accessing module with opens {package} to {B}, which makes it available at run time (with or without reflection) but only to {B}
  • open the entire module with open module {A} { ... }, which makes all its packages available at run time (with or without reflection) to all code

See this post for a more detailed discussion and comparison of these approaches.

Naman
  • 23,555
  • 22
  • 173
  • 290
Nicolai Parlog
  • 36,673
  • 16
  • 109
  • 236
  • Is `Lombok` in case 1 and hard to find a way to apply this flag to the JVM that will actually execute the reflecting code because it is part of the project's build process? – zhuguowei Jan 08 '17 at 04:03
  • Yes, Lombok is case 1. The question how hard it is to apply the flags belongs [here](http://stackoverflow.com/q/41520511/2525313). – Nicolai Parlog Jan 08 '17 at 09:07
  • 1
    Thanks for the explanation. For [my very similar case](https://github.com/graphhopper/graphhopper/issues/701#issuecomment-274295557) it [still fails](https://travis-ci.org/graphhopper/graphhopper/jobs/194113790) even with [that `--add-opens` option](https://github.com/graphhopper/graphhopper/commit/d43559a9f90cf72101e4f11c73c61a76c0dc9436). Strange. – Karussell Jan 22 '17 at 00:16
  • At first glance, the problem might be that you configure the Surefire plugin but the failure is produced by [the failsafe-plugin](https://travis-ci.org/graphhopper/graphhopper/jobs/194113790#L1077). If I am wrong, please ask a separate question. – Nicolai Parlog Jan 22 '17 at 18:28
  • 3
    @Nicolai I think you should update your answer because JDK 9 now allows illegal access by default and `--permit-illegal-access` will change to `--illegal-access`: http://mail.openjdk.java.net/pipermail/jigsaw-dev/2017-May/012673.html – ZhekaKozlov May 30 '17 at 04:45
  • @ZhekaKozlov I'm waiting for the first EA build that exposes that behavior before updating the answer. – Nicolai Parlog May 30 '17 at 07:48
  • `export JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS --add-opens=java.base/java.lang=ALL-UNNAMED"` worked for OpenJDK 9 because of https://bugs.openjdk.java.net/browse/JDK-8173128 – bartolo-otrit Oct 31 '17 at 11:47
  • Could you take a look at this question https://stackoverflow.com/questions/60918134/jpms-add-opens-doesnt-help-for-java-lang-reflect-inaccessibleobjectexception – Pavel_K Mar 29 '20 at 19:13
5

This is a very challenging problem to solve; and as noted by others, the --add-opens option is only a workaround. The urgency to resolve the underlying issues will only grow once Java 9 becomes publicly available.

I found myself on this page after receiving this exact Javassist error while testing my Hibernate-based application on Java 9. And since I aim to support Java 7, 8, and 9 on multiple platforms, I struggled to find the best solution. (Note that Java 7 and 8 JVMs will abort immediately when they see an unrecognized "--add-opens" argument on the command line; so this can't be solved with static changes to batch files, scripts, or shortcuts.)

It would be nice to receive official guidance from the authors of mainstream libraries (such as Spring and Hibernate), but with 100 days to go until the currently projected release of Java 9, that advice still seems hard to find.

After much experimentation and testing, I was relieved to find a solution for Hibernate:

  1. Use Hibernate 5.0.0 or higher (earlier versions won't work), and
  2. Request build-time bytecode enhancement (using the Gradle, Maven, or Ant plugins).

This avoids the need for Hibernate to perform Javassist-based class modifications at runtime, eliminating the stack trace shown in the original post.

HOWEVER, you should thoroughly test your application afterward. The bytecode changes applied by Hibernate at build-time appear to differ from the ones applied at runtime, causing slightly different application behavior. Unit tests in my app that have succeeded for years suddenly failed when I enabled build-time bytecode enhancement. (I had to chase down new LazyInitializationExceptions and other problems.) And the behavior seems to vary from one version of Hibernate to another. Proceed with caution.

David T
  • 385
  • 1
  • 3
  • 5
4

Using --add-opens should be considered a workaround. The right thing is for Spring, Hibernate and other libraries doing illegal access to fix their issues.

Alan Bateman
  • 4,734
  • 1
  • 14
  • 25
  • 1
    It'd be helpful to know how you suggest to "fix their issues". Is it by means of method or var handles? IMHO accessing state by reading/writing private fields isn't inherently bad, e.g. it's something explicitely foreseen by the JPA spec. – Gunnar Jul 22 '17 at 17:12
  • 4
    For the specific example, it looks like Hibernate or Spring using Javassist to hack into a non-public defineClass method. The Lookup.defineClass method was specifically added to help libraries inject classes so that it is the way forward for that use-case. For the case where JPA and other libraries that need to access private members of their consumers then they will need to document that the consumer opens the package to the library (for annotation based frameworks (like JPA) then that is potentially checkable at build time). – Alan Bateman Jul 22 '17 at 20:07
3

I had warnings with hibernate 5.

Illegal reflective access by javassist.util.proxy.SecurityActions

I added latest javassist library to dependencies gradle:

compile group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'

This solved my problem.