In the last post, we looked the concept of module declarations and module descriptors. We also used the two keywords exports
and requires
in a module declaration. This week we’ll look at some of the additional syntax that can be used with exports
and requires
.
If you missed the previous two posts, you can find them here:
In the last post we had the following module declaration:
module com.incusdata.office { requires com.incusdata.dao; exports com.incusdata.office.entity; exports com.incusdata.office.service; }
Java Modularity: The requires Directive
The requires
directive specifies that the module com.incusdata.office
depends on the com.incusdata.dao
module, i.e. there is a requirement for the com.incusdata.dao
module to be accessible to the com.incusdata.office
module.
A requires
module directive specifies that this module depends on another module — this relationship is called a module dependency.
The module com.incusdata.office
has both a runtime and a compile-time dependency on com.incusdata.dao
.
Java Modularity: The requires transitive Directive
We can specify implied readability by using requires transitive
. We can specify a dependency on another module, and we can ensure that other modules reading our module also read that dependency, as follows:
requires transitive modulename;
For example, the java.sql
module declaration includes the following directive:
requires transitive java.xml;
In this case, any module that reads java.sql
also implicitly reads java.xml
. If a method from the java.sql
module returns a type from the java.xml
module, code in modules that need java.sql
become dependent on java.xml
. If the java.sql
module didn’t use the requires transitive java.xml;
directive, none of the dependent modules would compile unless they also explicitly read java.xml
.
The requires transitive
directive is commonly used for aggregator modules. These are modules with no packages and only transitive requirements. The java.se
module is a good example. If we’re not interested in figuring out the fine-grained module dependencies, we can simply require java.se
and then access all Java SE modules.
The java.se
module is declared as follows:
module java.se { requires transitive java.compiler; requires transitive java.datatranser; requires transitive java.desktop; ... requires transitive java.sql; requires transitive java.sql.rowset; requires transitive java.xml; requires transitive java.xml.crypto; }
Standard Java SE modules can only grant implied readability to other standard Java SE modules. They are not allowed to grant implied readability to any non-standard modules, even if they depend on these non-standard modules. This ensures that code that depends only on standard modules is portable across all Java SE implementations.
Java Modularity: The requires static Directive
We can create an optional dependency by using requires static
. It is not very common to use this.
We use it when we write code in our module that references another module, but the users of our module will never use the other module directly. That module is required at compile time, but is optional at runtime, i.e. it is a compile-time only dependency. An example is if we want to use an annotation declared in a different module that must be processed at compile time.
Java Modularity: The exports Directive
An exports
module directive specifies a particular package in the module whose public types should be accessible to other modules. These public types (class
, interface
, enum
) include their nested public and protected types as well.
Using the office example again, the com.incusdata.office
module exports the two packages com.incusdata.office.entity
and com.incusdata.office.service
.
The exports
directive specifies that the public members of com.incusdata.office.entity
and com.incusdata.office.service
packages will be accessible by the dependent modules. Only the listed package itself is exported. No sub-packages of the exported package are exported. Private members are not accessible.
Java Modularity: The exports … to Directive
We use exports
to open up our public classes to the world. What if we don’t want everyone to be able to access our API? We can restrict which modules have access to our APIs using the exports ... to
directive. It is similar to the exports
directive, but we then list the modules that are allowed to import this package with their requires
module directive.
Using an exports ... to
directive allows us to specify which modules can access the exported package. This is known as a qualified export. We do this with a comma-separated list as follows:
module my.module { export com.my.package to com.other.module, com.another.module; }
Listing Module Contents
Remember that we can list all the modules in our JDK by running the following from the command line:
java --list-modules
We can see the exports
and requires
directives in action by viewing the contents of an individual module. We do this by by running the following from the command line:
java --describe-module module.name
First view some small modules like java.prefs
, java.compiler
, java.datatransfer
, or jdk.sql
, as in:
java --describe-module jdk.compiler
After that, have a look at some of the larger modules, such as the main java.base
module, or the java.desktop
and java.xml
modules.
When listing a module, we can see the packages that it exports, the packages it requires, the classes it uses, the internal packages it contains, etc. It should be starting to make a bit more sense now.
What’s Next?
In the next post, we’ll look at services in modules. These are specified with the uses
and provides
directives.
I’m always interested in your opinion, so please leave a comment. Your feedback helps me write tips that help you.