requires, exports, and qualified exports
Declaring inter-module dependencies and controlling which packages are visible.
requires
A module declares its compile-time and runtime dependencies with requires. Only exported packages from the required module are accessible:
module com.acme.app {
requires java.sql; // ordinary dependency
requires transitive java.logging; // re-exported to consumers
requires static java.annotation; // compile-time only
}
requires transitive X: any module that requires com.acme.app automatically also reads java.logging. Use this when your public API *returns* or *accepts* types from X — without it, callers would get a compile error because they don't directly require X themselves.
requires static X: X is available at compile time but is optional at runtime (no error if absent). Useful for annotation processors or optional integrations.
exports
Only packages explicitly listed in exports directives are readable by other modules. Everything else is strongly encapsulated — not accessible even via reflection by default:
module com.acme.lib {
exports com.acme.lib.api; // public to all modules
exports com.acme.lib.internal to com.acme.app; // qualified export
}
exports P to M is a qualified export: only module M can access package P. This is how the JDK exposes certain internal packages to specific platform modules without opening them to user code.
> Key rule: a package that is not exported cannot be accessed by another module, even if it contains public classes. public inside a non-exported package is only public *within the module*.