Requires, Exports and Opens in Java Modules

Naveen AnamaneniUpdated · 7 min read

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

DirectiveMeaning
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-info.java (missing requires)
module expenses.app { }
Compiler output
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:

Compiler output
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 error

Explanation: 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 Mrequires transitive M
This module can use MYesYes
Modules that require this module can use MNoYes
Use whenM is an internal detailM'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:

Main.java
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:

Terminal output
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.app

Add opens to the core module's descriptor:

expenses.core/module-info.java
module expenses.core {
    exports com.poolstring.expenses.model;
    exports com.poolstring.expenses.service;
    opens com.poolstring.expenses.service;
}
Terminal output
Private field read: []

Exports vs Opens

Pointexportsopens
Compile-time access to public typesYesNo
Run-time access to public typesYesYes
Reflection on private members (setAccessible(true))NoYes
Typical reasonOther modules call your APIA 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:

module-info.java
// 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

  1. Easy: Change expenses.core to export the internal package only to expenses.app using exports ... to.
  2. Harder: Add a third module that defines a ReportFormatter service interface, one implementation, and use ServiceLoader from expenses.app to find it.

Quick check: Module directives

4 questions
1Which directive lets other modules call a package's public classes?
2A framework must set private fields in your classes. Which directive is needed?
3What does requires transitive java.logging give users of your module?
4What error appears when a needed requires is missing?

Key Takeaways

Was this tutorial helpful?

Lesson 515 of 578