Understand JPMS
Java Platform Module System (JPMS) has been introduced since Java 9. With Java 9, JDK has been divided into 90 modules. This is a simple example created using IntelliJ IDEA.
As shown in the above diagram, there are three modules, Application, Service and Provider.
NOTE: Sorry about the violation of the module naming convention to simplify this example.
- Introduction
- Create in IntelliJ IDEA
- Module Dependencies
- Dynamic Discovery of implementation
- Command Line
- Find dependencies
- Distribution footprint
Introduction
The module is a set of exported and concealed packages.
-
Java modules categorised into two: Standard modules started with
java
. These modules are part of the Java SE specification. -
The modules which are not part of the Java SE specification.
The moudle java.base
is not depends on any other modules but every module depends on java.base
because java.base
module reference is implicitly included in all the other pacakges.
Create in IntelliJ IDEA
In the IntelliJ IDEA,
-
create a Empty project
-
Add the three IntelliJ IDEA Java modules: Application, Service and Provider.
-
In each Java Module, you have to create
module-info.java
files shown in the first screenshot.
As shown in the above screenshot, there is one module-info.java file for each IntelliJ IDEA Java module. The module name should be unique, although I used simple names for this example. To get the idea of naming, run the following command in the CLI to find the modules available with the Java runtime:
java --list-modules
Module descriptor module-info.class should be stored in the module root folder.
Module Dependencies
Here the three module descriptors source files (under src folder):
// Application
module Application {
requires Service;
uses com.github.ojitha.service.a.OjService;
}
// Provider
module Provider {
requires Service;
requires java.logging;
provides com.github.ojitha.service.a.OjService
with com.github.ojitha.provider.b.M;
}
//Service
module Service {
exports com.github.ojitha.service.a;
}
This is all about module dependencies such as
requires
: This module required other external modules:requires <module>
- Only
exports
ed packages are readable by the requiring module. - Any non
public
is not readable. - The implied readability is
requires transitive C
implies that the moduleC
is needed by this moduleB
as well as external moduleA
which uses this module.
- The use of
requires static <module-b>
implies thatmodule-b
is only required in the compile-time for this module. - The directive
requires java.base
is implicit dependency in all the descriptors.
- Only
exports
: The directiveexport <package>
defines that this module packages are allowed to access by other modules- Access is limited only to public classes by the modules which
requires
this module. - Using
exports <packages> to <module-e>
restrict the access of public classes of thispackages
only by themodule-e
whichrequires
this module.
- Access is limited only to public classes by the modules which
open
: In the directive,opens <pacakges>
, the entire package is allowed to access runtime only via Java reflection- Other module don’t need to specify
reuqires
explcitly to access package contents. - This can be restricted by
opens <packages> to <module-d>
allowing only to access runtime bymodule-d
. - Using
opens module <module-o> {}
, all the packages of themodule-o
is accessible to any other modules.
- Other module don’t need to specify
-
uses
: The directiveuses <service interface>
uses services provided by other modules -
provide
: In the service consumer module, the directiveprovides <service interface> with <implementation-classes>
:- specifies service interface or abstract class of the service module.
- The service consumer dynamically discover (discussed in the next section) the provider implementation classes.
- The consumer modules don not need to specify
requires
for the provider module.
version
: version of the module which is required for version control of modules.
NOTE: Circular module dependencies are not allowed.
Dynamic Discovery of implementation
-
The Service Java module contains only the interface, which is the contract:
package com.github.ojitha.service.a; public interface OjService { void printHello(); }
-
The above interface has been implemented in the Provider
package com.github.ojitha.provider.b; import com.github.ojitha.service.a.OjService; import java.util.logging.*; public class M implements OjService { private static final Logger log = Logger.getLogger(M.class.getName()); @Override public void printHello() { log.info("Hello"); } }
Here you can have several implementations for the same service (for example,
OjService
). -
The client is available in the Application module, which is the consumer
package com.github.ojitha.application.a; import com.github.ojitha.service.a.OjService; import java.util.ServiceLoader; public class HelloWorld { public static void main(String[] args) { ServiceLoader<OjService> sl = ServiceLoader.load(OjService.class); OjService l = sl.findFirst().get(); l.printHello(); } }
In the above code, the Provider implementation is dynamically selected, which is one of the great advantages of Module services.
-
Entry point is consumer, and you can run the
HelloWorld
main method.
It is easy to compile and run the application in the IntelliJ IDEA. Let’s see how to do this in the CLI.
Command Line
If you need to compile all the modules in CLI level, run the following command from the parent directory:
javac -d ./build --module-source-path "./*/src" $(find . -name "*.java")
Here the complete source and target after the compilation:
To run:
java -p build -m Application/com.github.ojitha.application.a.HelloWorld
Find dependencies
You can run the jdeps
utility to find the module dependencies:
jdeps build
This will show all the depenencies in the build
folder.
Distribution footprint
One of the great benefits of a modularised application is that the distribution footprint is very small.
Create a JIMAGE:
jlink --module-path build --add-modules Application,Service,Provider --bind-services --launcher Hello=Application/com.github.ojitha.application.a.HelloWorld --output Hello
You don’t need to install Java runtime to run this JIMAGE because Java runtime is already included in the Hello distribution.
To run this in another machine:
Hello/bin/Hello
Application load is quicker even because the dependencies between modules, missing modules and other errors can be verified before starting the application.
NOTE: Modules are load from the module-path instead of the class-path.