One of the most confusing challenges for developers migrating to the Java Platform Module System (JPMS) is dealing with hidden JDK modules and internal APIs. Before Java 9, developers freely used internal classes like sun.misc.Unsafe
. With JPMS, such APIs are encapsulated, leading to errors like:
java.lang.IllegalAccessError: class ... cannot access class ...
- “package is not visible” at runtime
To bypass this, Java introduced command-line options such as --add-exports
and --add-opens
, which temporarily open hidden modules and packages. While powerful, these flags can introduce risks if misused.
In enterprise systems and frameworks (Spring, Hibernate, JavaFX), reflective access often requires these flags during testing or deployment. Understanding how to use them correctly ensures smoother migrations and avoids brittle builds.
Think of hidden modules like locked filing cabinets in an office. --add-exports
is like copying documents out for others to read, while --add-opens
is giving a temporary master key for inspectors (reflection) to peek inside.
Understanding Hidden Modules
- Hidden Modules: Modules that aren’t explicitly exported or accessible by default.
- Examples:
jdk.internal.*
packages,sun.misc.*
APIs. - Purpose: Protect internal APIs from accidental or unsafe use.
The --add-exports Flag
Syntax
--add-exports <module>/<package>=<target-module>
Example
Allowing an internal package to be used by another module:
java --add-exports java.base/sun.nio.ch=ALL-UNNAMED -m com.example/com.example.Main
Here:
java.base/sun.nio.ch
is exported to all unnamed modules (classpath code).- Useful for libraries depending on internal JDK APIs.
The --add-opens Flag
Syntax
--add-opens <module>/<package>=<target-module>
Example
Opening a package for reflection:
java --add-opens java.base/java.lang=ALL-UNNAMED -m com.example/com.example.Main
Here:
java.base/java.lang
is opened for reflective access.- Required for frameworks like Spring and Hibernate which use reflection to inject dependencies or manipulate classes.
Best Practices & Pitfalls
✅ Best Practices
- Use
--add-exports
for compile-time visibility of internal APIs. - Use
--add-opens
only when frameworks need reflective access. - Prefer explicit module declarations over command-line hacks.
- Document all flags used in builds and CI/CD pipelines.
❌ Pitfalls
- Depending heavily on internal APIs (risk of removal in future JDKs).
- Using
ALL-UNNAMED
excessively (bypasses modular benefits). - Ignoring alternatives (public APIs, service loaders, explicit
opens
).
Example: Spring Boot with JPMS
Spring often requires reflective access:
java --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED -m com.example/com.example.MainApp
This ensures Spring can access classes reflectively without breaking JPMS rules.
What's New in Java Versions?
- Java 5–8 → N/A (no modules)
- Java 9 → Introduction of JPMS,
--add-exports
and--add-opens
introduced - Java 11 → Wider adoption, frameworks adapted with temporary flags
- Java 17 → More stable modular ecosystem; internal APIs further restricted
- Java 21 → No significant updates across Java versions for this feature
Real-World Analogy
Using --add-exports
and --add-opens
is like granting temporary visitor badges. Employees (modules) usually can’t access restricted rooms, but with these badges, they can enter. Overusing badges, however, undermines security policies.
Summary & Key Takeaways
- Hidden modules protect internal APIs from misuse
- Use
--add-exports
for compile-time visibility of hidden packages - Use
--add-opens
for reflective access (runtime frameworks) - Avoid relying on these flags long-term; prefer public APIs
- Document usage to ensure maintainability and future-proofing
FAQ: Hidden Modules and JPMS Flags
1. What is the difference between --add-exports
and --add-opens
?--add-exports
allows other modules to use APIs, while --add-opens
allows reflective access.
2. Why do I get “package is not visible” errors?
Because the package isn’t exported or opened, requiring --add-exports
or --add-opens
.
3. Can I replace --add-exports
with --add-opens
?
No, --add-opens
is for reflection, --add-exports
is for compilation/runtime usage.
4. Should I use ALL-UNNAMED
in production?
It works but weakens modular boundaries. Prefer explicit module names.
5. Do frameworks like Spring and Hibernate need these flags?
Yes, especially for reflection-based dependency injection.
6. How does JPMS improve security compared to classpath?
By hiding internal APIs unless explicitly exported or opened.
7. Should I use jlink
or jmod
with hidden modules?
Use jlink
to build minimal runtimes; hidden modules usually shouldn’t be included.
8. Can I modularize incrementally and still use these flags?
Yes, they help transition legacy apps by temporarily exposing hidden APIs.
9. What risks exist when using hidden modules?
Internal APIs may be removed without warning, breaking your application.
10. Do hidden modules impact performance?
No direct performance impact, but reliance on unstable APIs can affect long-term stability.