Requires, Exports and Opens in Java Modules
A module descriptor is made of directives. Three of them do most of the work: requires states which modules this one depends on, exports states which packages other modules may use at compile time and run time, and opens states which packages other modules may reach by reflection. Variations of these add finer control, and two more, uses and provides, connect modules through services. In this lesson, you will learn every directive, see the compiler and runtime errors that appear when one is missing, and understand the difference between exporting and opening a package.
The examples continue the expense tracker from "Java Platform Module System (JPMS)".
All the Directives
| Directive | Meaning |
|---|---|
requires M; | This module needs module M to compile and run. |
requires transitive M; | As above, and any module that requires this module can also use M. |
requires static M; | M is needed to compile but optional at run time. |
exports p; | Public types in package p can be used by any module. |
exports p to M1, M2; | Package p is exported only to the named modules. |
opens p; | Package p is open to deep reflection at run time (private members included). |
opens p to M; | Open for reflection only to the named modules. |
open module M { ... } | Every package of the module is open for reflection. |
uses S; | This module looks up implementations of service S with ServiceLoader. |
provides S with Impl; | This module supplies an implementation of service S. |
Requires: Declaring a Dependency
If expenses.app uses a class from expenses.core but its descriptor doesn't say so, the compiler refuses:
module expenses.app { }Main.java:3: error: package com.poolstring.expenses.service is not visible
import com.poolstring.expenses.service.ExpenseTracker;
^
(package com.poolstring.expenses.service is declared in module expenses.core, but module expenses.app does not read it)
1 error"Does not read it" is module language for "has no requires for it". Adding requires expenses.core; fixes it.
Exports: Sharing a Package
expenses.core has a package com.poolstring.expenses.internal that it doesn't export. Its class Rounding is public, but when the app tries to import it:
Main.java:3: error: package com.poolstring.expenses.internal is not visible
import com.poolstring.expenses.internal.Rounding;
^
(package com.poolstring.expenses.internal is declared in module expenses.core, which does not export it)
1 errorExplanation: This is strong encapsulation. public now means "public within the module, and outside it only if the package is exported". The author of expenses.core can change or delete Rounding freely, knowing no other module depends on it.
exports ... to narrows it further: exports com.poolstring.expenses.internal to expenses.app; would share the package with that one module only.
Requires Transitive: Passing a Dependency On
The JDK's java.sql module declares requires transitive java.logging;. Its API uses types from java.logging, so anyone using java.sql needs them too. With transitive, a module that writes requires java.sql; can use java.logging without requiring it separately.
requires M | requires transitive M | |
|---|---|---|
This module can use M | Yes | Yes |
Modules that require this module can use M | No | Yes |
| Use when | M is an internal detail | M's types appear in your public API (parameters, return types) |
Opens: Allowing Reflection
exports gives access to public types and members. Frameworks such as Spring, Hibernate and Jackson need more: they read and set private fields by reflection. For that, a package must be opened.
This version of Main reads the private field expenses of ExpenseTracker by reflection:
ExpenseTracker tracker = new ExpenseTracker();
Field f = ExpenseTracker.class.getDeclaredField("expenses");
f.setAccessible(true);
System.out.println("Private field read: " + f.get(tracker));With the package only exported, it compiles but fails when run:
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.List com.poolstring.expenses.service.ExpenseTracker.expenses accessible: module expenses.core does not "opens com.poolstring.expenses.service" to module expenses.appAdd opens to the core module's descriptor:
module expenses.core {
exports com.poolstring.expenses.model;
exports com.poolstring.expenses.service;
opens com.poolstring.expenses.service;
}Private field read: []Exports vs Opens
| Point | exports | opens |
|---|---|---|
| Compile-time access to public types | Yes | No |
| Run-time access to public types | Yes | Yes |
Reflection on private members (setAccessible(true)) | No | Yes |
| Typical reason | Other modules call your API | A framework inspects your classes |
A package can be both exported and opened, as above. A package of entity or data classes that only a framework touches is often opened but not exported.
Services: Uses and Provides
Services let modules cooperate without naming each other. One module defines an interface, another supplies an implementation, and a third finds whatever implementations are present:
// The module that looks up implementations:
module expenses.app {
requires expenses.core;
uses com.poolstring.expenses.service.Exporter;
}
// A module that supplies one:
module expenses.csv {
requires expenses.core;
provides com.poolstring.expenses.service.Exporter
with com.poolstring.expenses.csv.CsvExporter;
}The app calls ServiceLoader.load(Exporter.class) and receives every implementation on the module path. JDBC drivers are found this way: java.sql declares uses java.sql.Driver.
Common Mistakes
Exporting every package. That throws away the encapsulation modules exist to give.
Using exports when a framework needs opens. The symptom is InaccessibleObjectException at run time.
Forgetting transitive for a type in your API. Users of your module get "package is not visible" for a type your methods return.
Making the whole module open by habit. It works, and removes all protection against reflection.
Interview Corner
What is the difference between exports and opens in a module?
exports makes a package's public types accessible to other modules at compile time and run time, but does not permit reflective access to non-public members. opens makes a package accessible at run time only, including deep reflection on private members through setAccessible(true), and gives no compile-time access. Frameworks that use reflection, such as dependency injection and persistence libraries, need packages to be opened. Both have a qualified form, exports ... to and opens ... to, restricting access to named modules.
What does requires transitive mean?
requires transitive M means that this module depends on M and that any module requiring this module implicitly reads M as well. It is used when types from M appear in this module's public API, so that consumers don't have to add their own requires M. Plain requires keeps the dependency private to the module.
Practice
- Easy: Change
expenses.coreto export theinternalpackage only toexpenses.appusingexports ... to. - Harder: Add a third module that defines a
ReportFormatterservice interface, one implementation, and useServiceLoaderfromexpenses.appto find it.
Key Takeaways
Was this tutorial helpful?