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
order
module depends on theuser
module. - Classes in
order
can 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.api
is accessible to other modules. - Internal packages like
com.example.user.internal
remain 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;
}
opens
makescom.example.user.entity
available 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;
}
payment
requiresorder
, which requiresuser
.order
doesn’t re-exportuser
, so onlypayment
withrequires transitive
makes it available further down.- Entities in
payment
are 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
requires
defines dependencies between modules.exports
shares only intended packages, hiding internals.opens
enables controlled reflection for frameworks.- Overusing
open
orexports
weakens 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 module
opens everything.opens
opens 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
?
jlink
builds custom runtime images.jmod
packages 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.