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 @Component scanning
  • 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 transitive for API modules that expose types from dependencies
  • Use requires static for compile-only tools (annotation processors)
  • Prefer opens over exports when only reflection access is needed
  • Use provides/uses for pluggable service architectures
  • Run jdeps regularly to keep dependencies clean