When developers migrate to Java Modules (JPMS), one of the most frustrating issues they encounter is reflection not working as expected. Code that worked flawlessly on the classpath suddenly throws exceptions like:
java.lang.IllegalAccessException: class com.example.framework.ReflectionUtil
cannot access class com.example.user.entity.User (in module com.example.user)
This happens because JPMS enforces strong encapsulation, and reflection must explicitly be allowed with opens
. Understanding how access control and reflection work in modules is critical for building modern enterprise systems, especially when using frameworks like Spring, Hibernate, or Jackson, which rely heavily on reflection.
This tutorial explains deep reflection issues in JPMS, how to fix them, and best practices to keep applications both secure and functional.
Access Control Basics in JPMS
1. Exports vs Opens
exports
→ Makes a package visible to other modules at compile-time and runtime.opens
→ Makes a package available for deep reflection at runtime, but not at compile-time.
Example
module com.example.user {
exports com.example.user.api; // Public API
opens com.example.user.entity to hibernate.core; // Reflection for ORM
}
- Other modules can compile against
com.example.user.api
. - Hibernate can reflectively access
com.example.user.entity
, but other modules can’t.
2. Open Modules
open module com.example.test {
exports com.example.test.api;
}
- An open module means all its packages are open to reflection.
- Useful in testing or debugging, but dangerous in production.
3. Deep Reflection in Frameworks
Frameworks often require reflective access to private fields, methods, and constructors. JPMS blocks this unless opens
is used.
Example with Hibernate
module com.example.user {
opens com.example.user.entity to org.hibernate.orm.core;
}
This allows Hibernate to perform ORM mapping via reflection without exposing the package as API.
Pitfalls in Reflection with JPMS
-
Confusing Exports and Opens
exports
doesn’t allow reflection.opens
doesn’t allow compile-time use.
-
Overusing open modules
- Leads to security risks by exposing everything.
-
Forgetting Targeted Opens
- Always open packages to specific frameworks, not globally.
-
Relying on Automatic Modules
- Reflection can behave unpredictably when using unnamed or automatic modules.
Best Practices for Reflection in JPMS
- ✅ Export only stable APIs.
- ✅ Use
opens
selectively for reflection-heavy frameworks. - ✅ Avoid
open module
in production. - ✅ Organize entities and reflection-dependent classes into separate packages.
- ✅ Document why a package is opened in
module-info.java
. - ✅ Test with frameworks (Hibernate, Spring) to ensure correct module boundaries.
📌 What's New in Java Versions?
- Java 5 → N/A (modules introduced in Java 9)
- Java 9 → JPMS introduced strong encapsulation and reflection rules (
exports
,opens
) - Java 11 → Tooling support for debugging reflection issues improved
- Java 17 → Security and performance improvements for reflective access
- Java 21 → No significant updates across Java versions for this feature
Analogy
Think of reflection in JPMS like a secure office building:
- Normal visitors (other modules) can only access public areas (
exports
). - Auditors (frameworks) need special badges (
opens
) to look into private files. - Making the entire building open (
open module
) is like giving everyone unrestricted access—convenient but dangerous.
Summary + Key Takeaways
- JPMS enforces strong encapsulation, breaking old reflection assumptions.
- Use
exports
for API visibility,opens
for reflection. - Avoid
open module
; prefer targetedopens
. - Organize reflection-heavy code into dedicated packages.
- JPMS makes applications more secure, maintainable, and framework-friendly when configured properly.
FAQs
Q1. What is the difference between the classpath and module path?
Classpath exposes everything globally; module path enforces access rules.
Q2. Why do I get “package is not visible” errors?
Because the package was not exported in module-info.java
.
Q3. Why does reflection fail in JPMS?
Because the package was not opened with opens
.
Q4. What is the difference between open
and opens
?
open module
→ all packages open for reflection.opens
→ only specific packages are open for reflection.
Q5. What is the purpose of requires transitive
?
It re-exports dependencies for downstream modules.
Q6. What are automatic modules, and should I use them?
They’re JARs treated as modules. Good for migration, not production.
Q7. How does JPMS improve security compared to classpath?
By hiding internals, restricting reflection, and preventing split packages.
Q8. When should I use jlink
vs jmod
?
jlink
→ Custom runtime images.jmod
→ Package modules for distribution.
Q9. Can I migrate legacy projects incrementally?
Yes, start with automatic modules and gradually modularize.
Q10. Do frameworks like Spring and Hibernate fully support JPMS?
They work, but usually need selective opens
for reflective access.