A common mistake developers make when modularizing Java applications is forgetting to properly configure module-info.java, leading to errors like “package is not visible” or frameworks failing at runtime due to missing reflection access. These issues confuse teams migrating from the classpath world, where everything was visible everywhere, to the module path where visibility is strictly controlled.
Understanding the three key directives—requires, exports, and opens—is essential for building modular applications. These determine how your modules depend on others, what parts of your code are shared, and how frameworks can still use reflection when needed. In large systems, mastering this prevents spaghetti-like dependencies and strengthens security.
The Role of module-info.java
The module-info.java file defines the metadata of a module. Its core purpose is to:
- Declare dependencies (
requires) - Expose packages to other modules (
exports) - Allow reflective access for frameworks (
opens) - Configure services (
uses,provides)
Without it, a JAR is treated as an unnamed or automatic module, offering little benefit from JPMS.
Requires: Declaring Dependencies
The requires directive specifies which modules a module depends on.
Example
module com.example.order {
requires com.example.user;
}
- The
ordermodule depends on theusermodule. - Classes in
ordercan access exported packages fromuser.
Variants
requires transitive– Re-exports the dependency.
module com.example.payment {
requires transitive com.example.order;
}
Now, any module that requires payment also gets access to order.
requires static– Dependency needed at compile-time but optional at runtime.
Exports: Sharing Packages
The exports directive controls what packages are visible to other modules.
Example
module com.example.user {
exports com.example.user.api;
}
- Only
com.example.user.apiis accessible to other modules. - Internal packages like
com.example.user.internalremain hidden.
This enforces strong encapsulation and prevents accidental misuse.
Pitfall
If you forget to export a package, dependent modules will throw compilation errors.
Opens: Allowing Reflection
Some frameworks (e.g., Hibernate, Spring) rely heavily on reflection. JPMS blocks reflection on non-exported packages unless explicitly opened.
Example
module com.example.user {
opens com.example.user.entity to hibernate.core;
}
opensmakescom.example.user.entityavailable for reflection.- Restricting it to a specific module (like Hibernate) is safer than opening it globally.
Open Module vs Opens
open module→ All packages open for reflection (not recommended for security).opens→ Granular, package-specific reflection access.
Real-World Example
A payment processing system with three modules:
// user module
module com.example.user {
exports com.example.user.api;
}
// order module
module com.example.order {
requires com.example.user;
exports com.example.order.api;
}
// payment module
module com.example.payment {
requires transitive com.example.order;
exports com.example.payment.api;
opens com.example.payment.entity to hibernate.core;
}
paymentrequiresorder, which requiresuser.orderdoesn’t re-exportuser, so onlypaymentwithrequires transitivemakes it available further down.- Entities in
paymentare open only for Hibernate, not all modules.
Pitfalls & Misuse Cases
- Exporting Everything – Defeats encapsulation, like classpath behavior.
- Open Modules – Makes the entire module reflective, increasing attack surfaces.
- Requires Transitive Overuse – Creates hidden dependencies and coupling.
- Forgetting Requires Static – Leads to runtime failures when optional libraries are absent.
Best Practices
- Export only API packages, not internal ones.
- Use opens sparingly and restrict it to specific modules.
- Avoid open module unless prototyping.
- Document dependencies clearly when using
requires transitive. - Combine JPMS with build tools like Maven/Gradle for consistency.
📌 What's New in Java Versions?
- Java 5 → N/A (modules introduced in Java 9)
- Java 9 → Introduction of JPMS,
module-info.java, modular JDK - Java 11 → Refinements and tooling improvements (
javac,jlink) - Java 17 → Performance and security refinements for modules
- Java 21 → No significant updates across Java versions for this feature
Analogy
Think of a library system:
requires→ Your library subscribes to another’s collection.exports→ You decide which sections of your library are open to others.opens→ You allow inspectors (frameworks) behind the scenes for special checks.
This keeps boundaries clear while allowing controlled collaboration.
Summary + Key Takeaways
requiresdefines dependencies between modules.exportsshares only intended packages, hiding internals.opensenables controlled reflection for frameworks.- Overusing
openorexportsweakens modularization. - JPMS improves clarity, maintainability, and security for modern Java projects.
FAQs
Q1. What is the difference between the classpath and module path?
Classpath exposes everything globally; module path enforces encapsulation with exports/requires.
Q2. Why do I get “package is not visible” errors when using modules?
Because the package wasn’t exported in module-info.java.
Q3. What is the purpose of requires transitive?
To re-export dependencies so downstream modules don’t need to declare them explicitly.
Q4. How do open and opens differ in reflection?
open moduleopens everything.opensopens a specific package, often restricted to frameworks.
Q5. What are automatic modules, and should I use them?
JARs without descriptors treated as modules. Use temporarily in migration, not in long-term solutions.
Q6. How does JPMS improve security compared to classpath?
By hiding internal packages and limiting reflection access.
Q7. When should I use jlink vs jmod?
jlinkbuilds custom runtime images.jmodpackages modules for distribution.
Q8. Can I migrate a legacy project to modules incrementally?
Yes, start with one modularized JAR and migrate others progressively.
Q9. How do I handle libraries that aren’t modularized?
Use them as automatic modules or keep them on the classpath.
Q10. Do frameworks like Spring/Hibernate fully support modules?
Partial support. They often require opens for reflection to work.