Hidden Modules and --add-exports / --add-opens Flags in Java

Illustration for Hidden Modules and --add-exports / --add-opens Flags in Java
By Last updated:

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.