20250205 ‐ solve java problem - cywongg/2025 GitHub Wiki

What does this error mean?

When you see an error like:

Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int)
throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @45ca843

it usually indicates that some code in your application (or in one of the libraries you depend on) is using reflection to access internal methods in the Java runtime. Methods such as ClassLoader.defineClass(...) are part of java.lang, which is in the base module (java.base). Starting with Java 9, the [Java Platform Module System (JPMS)]1 added strong encapsulation of Java internal APIs. This means that packages inside java.base are no longer accessible by default for reflective calls.

Even though these restrictions began in Java 9, sometimes they weren’t consistently enforced in Java 11 in the same way as in Java 17. As Java versions progress, the enforcement of the module system’s encapsulation rules has gotten stricter. That’s why something that used to work “fine” (perhaps in Java 11) can suddenly break in Java 17.

What does adding VM option -add-opens java.base/java.lang=ALL-UNNAMED mean?

The -add-opens JVM argument basically relaxes the module system’s encapsulation for specific packages. Concretely:

  • -add-opens java.base/java.lang=ALL-UNNAMED means: “Open the package java.lang (which is in the java.base module) to all unnamed modules.”

An “unnamed module” is basically all code on the classpath that hasn’t been turned into a named module (using a module-info.java). So this flag allows reflective access from your traditional classpath code to that internal package/method. Essentially, using -add-opens is a way of telling JPMS: “Go ahead and allow reflection on that internal API as if we were still on Java 8 (or an earlier non-modular environment).”

Why upgrading to Java 17 requires adding this option?

Under the hood, your code or a library you’re using is performing reflective calls to methods like ClassLoader.defineClass(...). Java 17 enforces module boundaries more strictly than older versions did. If your code or library tries to reflectively access these methods, Java 17 will raise an error unless you explicitly open the corresponding package to reflection.

Hence, you need to pass the -add-opens option (or something equivalent) to make that reflective call permissible, or otherwise refactor your code so it does not rely on these internal calls.

How can I run my app successfully without adding -add-opens?

  1. Upgrade your libraries:
    Check whether an updated version of the library you use has removed or replaced the usage of those internal APIs. Many libraries that used to rely on setAccessible or defineClass calls have introduced JPMS-friendly alternatives. If you can upgrade such libraries to a later version, you may no longer need the -add-opens option.

  2. Use official / public APIs:
    If it’s your own code that is using this reflective logic, see whether there is an official or safer public API that can replace the internal call. For example:

    • For dynamically generating classes or bytecode manipulation, libraries like [Byte Buddy]2 or [ASM]3 often provide a more stable API that does not rely on ClassLoader.defineClass in a non-open package.
    • For reflection-based libraries, ensure they are configured in a modular way or have alternative strategies for scanning classes without relying on blocked internals.
  3. Modularize your application:
    If you create your own module descriptors (module-info.java) and name your modules properly, you could use module directives like opens or exports within your own modules. However, you cannot open the java.base/java.lang package from within your own module descriptor—only the JDK maintainers can do that. But for your own packages, you can reduce or remove the need for -add-opens by exposing only the minimal required accessibility.

  4. Refactor or remove the risky reflection:
    If possible, the safest bet is to remove the reflection calls that target private or protected JDK internals. This might involve rewriting some parts of your code or library usage. This can be the most future-proof approach, as it avoids relying on flags that might break in later JDK versions.

Summary

  • Java 9+ introduced the Java Platform Module System, which enforces strong module boundaries.
  • Some internal Java APIs (like ClassLoader.defineClass) are no longer reflectively accessible by default.
  • -add-opens java.base/java.lang=ALL-UNNAMED reverts part of that encapsulation for unnamed modules, allowing older reflection-based libraries or code to keep working.
  • If you do not want to use -add-opens, you need to upgrade or refactor so that your application no longer depends on those protected or private JDK internals.


Here's a more beginner-friendly explanation of what’s going on, why you are seeing the error, and how you can troubleshoot it.


What is Reflection?

In Java, “reflection” is when your code looks at (or tries to call) classes, methods, or fields at run time without knowing about them at compile time. For example, using reflection, you can do things like change the value of a private field in a class, call methods that aren’t normally accessible, or even create new classes on the fly. This can be powerful, but it’s also risky because it can break if the underlying Java classes change.


What is the Java Module System?

Starting with Java 9, Java introduced a new Module System (sometimes called JPMS for “Java Platform Module System”). You can think of a module as a way to group related packages and classes together so that different parts of Java (and different libraries) can be more strictly separated from each other.

Before Java 9, everything was more or less wide open—reflection could access many private or internal parts of the JDK (Java’s own classes). After Java 9, if a particular class or package isn’t explicitly “opened” (meaning allowed to be accessed reflectively), you will get errors when you try to reflect on it.


Why Does This Error Appear?

When you see an error like:

Unable to make protected final java.lang.Class ...
module java.base does not "opens java.lang" to unnamed module

it usually means that some code (either your own code or a library you use) is using reflection to call an internal method of the Java library (usually in java.lang, java.util, etc.). Under the new module rules, that internal method isn’t allowed to be accessed anymore unless you explicitly give permission via special flags.


What is the “-add-opens” VM Option?

When you pass something like:

--add-opens java.base/java.lang=ALL-UNNAMED

to your Java virtual machine (JVM), you are basically telling Java:

“Hey, I know you normally won’t let me or my libraries reflect on the internal classes of java.lang, but I want you to open it up anyway.”

VM Option” just refers to any argument you pass to the Java Virtual Machine when you start your app or run it from IntelliJ. Another example is -Xmx1024m for setting max memory. Similarly, --add-opens is a special option for controlling module access.


How to Identify the Reflection Causing the Problem

1. Check the Error Message / Stack Trace

Often, the error message or stack trace will show you which class is trying to do the reflective call. Look closely at the lines in the error details—it might show something like:

at com.someLibrary.SomeClass.someMethod(SomeClass.java:123)
at ...

This can help you figure out if it’s coming from a library (for example, a mocking framework, a bytecode manipulation tool, or something like Spring).

2. Search for Common Reflection Indicators

If it’s your own code, try searching your codebase for methods like:

  • setAccessible(true),
  • Unsafe,
  • defineClass,
  • or libraries like “ByteBuddy” or “Javassist”.

These are often used to do low-level reflection or bytecode manipulation.

3. Look at Your Libraries or Dependencies

Sometimes libraries do these reflections in their internals. If you suspect that, you could:

  • Update the library to a newer version (they might have removed or fixed the reflection workaround for newer Java versions).
  • Look at the library’s documentation or GitHub issues. People often report these reflective-access issues after upgrading to Java 16 or 17.

4. Run with Higher Verbosity

Run your application with the --illegal-access=debug (on older JDKs) or similar flags to try and see more details about illegal reflective calls. This might point you to the exact place that’s causing the issue.


How to Fix It Without Using -add-opens

  1. Update Your Libraries:
    Many libraries that used reflection in older versions now have updated approaches that don’t rely on breaking into the JDK internals. If you’re using an outdated library, upgrade it so you don’t need -add-opens.

  2. Change Your Own Code:
    If the reflection is in your own code, see if you can replace those calls with standard Java APIs. For example, if your code is calling ClassLoader.defineClass directly, see if there’s a safer way to load classes or if you can use something like the official Java reflection classes in a way that doesn’t target private/protected methods in java.lang.

  3. Rewrite Your Code to Avoid Private Stuff:
    Ideally, avoid depending on private or protected fields/methods of JDK classes. If a method is private or restricted, there might be a reason! If you rely on it, Java can break your code in future updates because private methods can change at any time.


Summary (Like a Cheat Sheet)

  1. You see an error about “module does not open java.lang” because your code or a library is using reflection on JDK internals.
  2. -add-opens java.base/java.lang=ALL-UNNAMED tells the JVM to allow that reflection anyway.
  3. If you want to avoid -add-opens, find who’s doing the reflection, update the library or refactor your code so it doesn’t break into JDK internals.

Bottom Line:
• The Java Module System locks down internal classes by default.
• Reflection that worked in older Java versions can fail now if it targets restricted classes.
• Using the -add-opens VM option is a quick fix to open them again, but a more permanent fix is to update your code or libraries to use public (and supported) APIs.