A frequent headache developers face when working with Java Modules is the “package not visible” error, even though the dependency exists somewhere in the project. The culprit? Forgetting to propagate dependencies correctly. On the classpath, everything was accessible globally, but with JPMS, you must explicitly declare dependencies.
This is where requires transitive
comes in. It allows dependency propagation so that downstream modules automatically gain access to the dependencies of the modules they depend on. This is particularly important in large enterprise systems, microservices, and layered architectures, where exposing stable APIs to multiple modules is critical.
What is requires transitive
?
The requires transitive
directive in module-info.java
re-exports a dependency. Any module that depends on your module will also have access to the re-exported dependency without declaring it explicitly.
Example
// order module descriptor
module com.example.order {
requires transitive com.example.user;
exports com.example.order.api;
}
Here:
order
depends onuser
.- By marking it as
requires transitive
, any module that depends onorder
will also automatically get access touser
.
Real-World Example
Imagine a shopping application with three modules:
- User Module (
com.example.user
)
module com.example.user {
exports com.example.user.api;
}
- Order Module (
com.example.order
)
module com.example.order {
requires transitive com.example.user;
exports com.example.order.api;
}
- Payment Module (
com.example.payment
)
module com.example.payment {
requires com.example.order;
exports com.example.payment.api;
}
What happens?
payment
depends onorder
.- Since
order
usesrequires transitive
foruser
, thepayment
module automatically gets access touser
APIs. - Without
transitive
,payment
would have to declarerequires com.example.user;
separately.
This reduces boilerplate and ensures dependency propagation across layers.
Pitfalls and Misuse Cases
- Overusing
requires transitive
→ Leads to unnecessary exposure of internals and hidden coupling. - Leaky Abstractions → If a module re-exports too much, downstream modules may accidentally rely on unintended APIs.
- Version Conflicts → Complex graphs with transitive dependencies can cause ambiguity when versions differ.
- Wrong Placement → Declaring
requires transitive
in every module defeats its purpose. Reserve it for stable API modules.
Best Practices
- Use
requires transitive
for API modules that are widely reused. - Avoid applying it to implementation details or unstable modules.
- Keep module boundaries clear to prevent dependency sprawl.
- Document why a dependency is made transitive in project guidelines.
- Regularly review
module-info.java
to ensure only intentional propagation.
📌 What's New in Java Versions?
- Java 5 → N/A (modules introduced in Java 9)
- Java 9 → JPMS introduced with
requires transitive
support - Java 11 → Better tooling to detect dependency issues in modular apps
- Java 17 → Minor refinements for JPMS performance/security checks
- Java 21 → No significant updates across Java versions for this feature
Analogy
Think of modules as office departments:
- The User Department provides employee data.
- The Order Department depends on User and shares its reports with Finance.
- The Payment Department depends on Order, and thanks to requires transitive, it automatically gets access to User data without needing a direct subscription.
This ensures smooth workflows without redundant requests.
Summary + Key Takeaways
requires transitive
propagates dependencies to downstream modules.- It’s ideal for shared API modules in layered systems.
- Avoid overuse, as it can create tight coupling and leaky abstractions.
- JPMS enforces explicit boundaries, making codebases cleaner, safer, and maintainable.
FAQs
Q1. What is the difference between the classpath and module path?
Classpath exposes everything globally; module path enforces explicit requires/exports.
Q2. Why do I get “package is not visible” errors when using modules?
Because the dependency wasn’t explicitly required or re-exported via requires transitive
.
Q3. What is the purpose of requires transitive
?
It propagates dependencies so downstream modules don’t need to declare them separately.
Q4. How do open
and opens
differ in reflection?
open module
opens everything.opens
opens specific packages, often restricted to frameworks.
Q5. What are automatic modules, and should I use them?
JARs without descriptors treated as modules. Use temporarily during migration, not in production.
Q6. How does JPMS improve security compared to classpath?
By hiding internals, controlling what’s exported, and limiting reflection.
Q7. When should I use jlink
vs jmod
?
jlink
builds slim runtime images.jmod
packages modules for distribution.
Q8. Can I migrate a legacy project to modules incrementally?
Yes, modularize one JAR at a time and use automatic modules as a bridge.
Q9. How do I handle third-party libraries that aren’t modularized?
Use them as automatic modules or place them on the classpath.
Q10. Do frameworks like Spring/Hibernate fully support modules?
Not fully. Many require opens
for reflection to function correctly.