On this page
Module Dependencies
Module dependencies in JPMS are declared explicitly in module-info.java. This page covers the key directives and patterns.
requires
Declare a dependency on another module:
module com.example.app {
requires java.sql; // compile-time and runtime dependency
requires transitive java.logging; // re-exported to consumers
requires static lombok; // optional, compile-time only
requires com.example.util; // another application module
}
| Modifier | Meaning |
|---|---|
| (none) | Required at compile time and runtime |
transitive |
Consumers of this module implicitly read the required module |
static |
Optional dependency — not needed at runtime |
transitive Example
module com.example.lib {
requires transitive java.sql;
exports com.example.lib;
}
Any module that requires com.example.lib automatically reads java.sql — useful for library modules.
exports
Make a package accessible to other modules:
module com.example.app {
exports com.example.app.api;
exports com.example.app.dto to com.example.web, com.example.test;
}
Without exports, a package is encapsulated — accessible only within the module.
opens
Grants reflective access without exporting:
module com.example.app {
opens com.example.app.entity; // all modules (reflection)
opens com.example.app.config to spring.beans; // specific module only
}
Required for:
- JPA/Hibernate entity scanning
- Spring
@Componentscanning - Jackson JSON deserialization
- Any framework using reflection to access private members
provides / uses — Service Loading
JPMS integrates with ServiceLoader:
// API module
module com.example.spi {
exports com.example.spi;
}
// Provider module
module com.example.impl {
requires com.example.spi;
provides com.example.spi.PaymentService
with com.example.impl.CreditCardPayment;
}
// Consumer module
module com.example.app {
requires com.example.spi;
uses com.example.spi.PaymentService;
}
Loading at runtime:
ServiceLoader<PaymentService> loader =
ServiceLoader.load(PaymentService.class);
for (PaymentService service : loader) {
service.process();
}
Qualified vs Unqualified
exports com.example.api; // all modules
exports com.example.internal to com.example.test; // qualified — only test module
opens com.example.entity; // all modules (reflection)
opens com.example.dto to hibernate.core; // qualified
Split Packages
Two modules cannot export the same package. If found, compilation fails. Solutions:
- Merge modules
- Rename packages
- Use
--patch-module(testing only)
Checking Module Dependencies
# Analyze JAR dependencies
jdeps myapp.jar
# Module resolution at runtime
java --module-path out --module com.example.app/com.example.Main \
--validate-modules
# Show module graph
jdeps --module-path out --module com.example.app --list-deps
Common Patterns
Library Module
module com.example.lib {
requires transitive java.sql;
exports com.example.lib.api;
opens com.example.lib.internal to com.fasterxml.jackson.databind;
}
Application Module
module com.example.app {
requires com.example.lib;
requires java.logging;
opens com.example.app to spring.core;
}
Test Support
module com.example.app {
exports com.example.app to com.example.test;
opens com.example.app.entity to org.hibernate.orm.core;
}
Best Practices
- Use
requires transitivefor API modules that expose types from dependencies - Use
requires staticfor compile-only tools (annotation processors) - Prefer
opensoverexportswhen only reflection access is needed - Use
provides/usesfor pluggable service architectures - Run
jdepsregularly to keep dependencies clean