Java Modules (JPMS) Cheat Sheet
Project Jigsaw, The Java Platform Module System (JPMS), Modularity, JSR 277, JSR 376... These are the names of one of the most significant features of Java 9. Modularity reduces the complexity of the system by providing:
- Explicit dependencies
- Clear interface
- Strong encapsulation
Modularity is also arguably one of the terrifying Java features of all times. Despite these numerous benefits, proper modules are still rare species in the Java world.
The purpose of this cheat sheet is to show that modules in Java are friendly to us, developers, and easy to use. The Module Declaration (module-info.java) section explains how to define modules. The Module Path vs Class-Path section will be helpful for people migrating existing non-modular applications to JPMS.
Table of Contents
Module Declaration (module-info.java)
- Module Name and Minimal Module Declaration
- Dependencies
- API
- Other Stuff: Services, Annotations, and Formal grammar of the module declaration file
FAQ
Here are some of important questions about support of old-style non-modular jar in Java 9+. It's better than what you think!
Will my plain old jar (without a module declaration) work?
...and...
Will my modularized jar work with dependencies which are not modularized yet?
YES! and YES!
But... if...
Read more:
In JPMS, you can convert existing non-modular jars into:
- Unnamed modules, when passed on classpath;
- Or, automatic modules, when passed on module path.
See Module Path vs Class Path.
module-info.java
)
Module Declaration (A module declaration is a file which defines the dependency graph:
- Dependencies: Which other modules this module depends on;
- API: which parts of this module other modules can use: at compile time or at runtime.
To create a module declaration:
module-info.java
com
or org
folders.
Module Name and Minimal Module Declaration
A minimal module declaration defines its name:
module com.mycompany.myproject {
}
|
Module's Dependencies
Dependencies are other modules this module needs for its work - it requires these modules.
module com.mycompany.myproject { // the module name should be unique
// dependency on `com.example.foo.bar` which must be present both at compile time AND runtime
requires com.example.foo.bar;
// transitive dependency: any module which requires current module will also *implicitly* depend on this dependency
requires transitive com.kitties.bar;
// optional dependency is mandatory at compile time, but optional at runtime
requires static com.owls.foo;
}
Read more:
Module's API
In pre-Java 9 world any public class was available everywhere and could be used by any other class. The "internal" packages were not in fact internal. With Java 9, module's contents are not available to the external classes by default; one must explicitly define what the module exposes to outside world so that other modules could use it either in compile time or via reflection. Module's API is now expressly specified in the module's declaration.
Compile Access |
Reflection Access |
||||
---|---|---|---|---|---|
What |
By Whom |
What |
By Whom |
||
Export Package |
Unqualified |
|
All Code |
|
All Code |
Qualified |
Modules from the "to" clause | Modules from the "to" clause | |||
Open Package |
Unqualified |
NO access | All Code |
|
All Code |
Qualified |
Modules from the "to" clause | Modules from the "to" clause |
How to export or open a package
A package can be exported or opened in
😃 Module declaration (module-info.java
file). It's a recommended approach when you have control over this file.😟 Or, as a command line option, if you have no other choice.
Module Descriptor |
Java Command Line Options |
||
---|---|---|---|
Export Package |
Unqualified |
exports packageName |
n/a |
Qualified |
exports packageName to targetModule
|
--add-exports sourceModule/packageName=targetModule(,targetModule)* The target-module can be ALL-UNNAMED to export to all unnamed modules |
|
Open Package |
Unqualified |
opens packageName |
n/a |
Qualified |
opens packageName to targetModule |
--add-opens sourceModule/packageName=targetModule(,targetModule)* |
Read more:
Exported Packages
The exports
directive defines what this module exposes to other modules at runtime and compile time.
module com.mycompany.myproject { // the module name should be unique
// unqualified export => grants access to ALL other modules
exports com.mycompany.myproject.foo;
exports com.mycompany.myproject.foo.internal
// having a "to" clause means that this is a **qualified export** => exports the package only to the friends of the current module - the modules specified in the `to` clause
to com.example.x;
exports com.mycompany.myproject.bar.internal to com.example.y, com.example.z;
}
Packages Open for Reflection
The opens
directive opens the module to the outside modules for the reflection. It's usually useful when using reflection-based frameworks such as Spring or Guice;
module com.mycompany.myproject { // the module name should be unique
// `opens` the package for reflection to ALL other modules
// * compile time: no access at all
// * runtime: to all types, including private types, and all their members, including private ones
opens com.mycompany.myproject.ollie;
// a qualified `opens` directive - only modules from the `to` cause can access this package at runtime
opens com.mycompany.myproject.ollie.internal to org.anothercompany.coolframework;
// Also see a section about the open modules - a module which opens ALL its packages
}
Shortcut: an open module
An open module opens
all its packages. To define it, add the open
modifier before the module
:
open module com.mycompany.myproject {
}
Advanced: Services for DI
Services implemented with use of ServiceLoader
existed in Java well before Java 9. Their goal is to do decouple the interface and its implementation(s). Now, in Java 9, we can declare services and their usages in the module-info.java
:
Service consumer:
module com.mycompany.serviceconsumer {
// Use an existing service. The implementation can be accessed in code by using the ServiceLoader
uses com.example.foo.ServiceInterface;
}
Service provider:
module com.mycompany.service {
// Register a new service implementation for com.example.foo.ServiceInterface
provides com.example.foo.ServiceInterface
with com.example.foo.ServiceImpl;
}
|
Advanced: Annotations in Module Declarations
Module declarations can also have annotations, e.g., the @Deprecated
annotations:
/**
* This module was replaced by a new module performing the same job much better!
* @deprecated use {@link com.mycompany.newcoolmodule} instead.
*/
@Deprecated
module com.mycompany.oldmodule {}
Advanced: The formal grammar of the module declaration file
OK, open modules, annotations... What else?
ModuleDeclaration:
{Annotation} [open] module Identifier{.Identifier}
{ {ModuleDirective} }
ModuleDirective:
requires {RequiresModifier} ModuleName ;
exports PackageName [to ModuleName {, ModuleName}] ;
opens PackageName [to ModuleName {, ModuleName}] ;
uses TypeName ;
provides TypeName with TypeName {, TypeName} ;
RequiresModifier:
(one of)
transitive static
ModuleName:
Identifier{.Identifier}
Module Path vs Class Path
Command line |
Modular JAR (JAR with a module-info.class) |
Plain JAR |
|
---|---|---|---|
Module Path |
|
Explicitly defined module | Automatic module |
Classpath |
|
Unnamed module | Unnamed module |
You can mix module path and classpath!
Read more:
Types of Modules: Named and Unnamed
As you can see from the table above:
- Everything on the module path, regardless whether it's a plain jar or a jar with a module-info, becomes a named module.
- Similarly, everything on the classpath, regardless whether it's a plain jar or a jar with a module-info, becomes an unnamed module.
In fact, there are three module types:
- Named:
- Explicit
- Automatic
- Unnamed
Explicit Modules
Explicit modules follow the rules for Dependencies and API defined in its Module Declaration (module-info.java)
Automatic Modules
Automatic modules are plain JARs (no module descriptor) on the module path.
"Module1 reads Module2" means that Module1 can access types from Module2's exported packages. A module that requires another module also reads the other module. |
As they don't have a module-info.class
, this information will be calculated using the following rules:
- Name:
Automatic-Module-Name
fromMANIFEST.MF
if it's present. Derived from the JAR name otherwise.- They can be referenced using this name;
- See http://branchandbound.net/blog/java/2017/12/automatic-module-name/ for details;
- For Eclipse plug-ins, see https://dev.eclipse.org/mhonarc/lists/cross-project-issues-dev/msg14888.html;
- Dependencies:
- It
requires transitive
all other resolved modules; - Reads the
unnamed
module;
- It
- API:
- Exports all its packages;
- The module can be referenced in other modules'
module-info.java
using its calculated name.
Unnamed Module
Everything on the classpath becomes an unnamed module.
Name: No name;- Dependencies:
- Reads all other modules
- Can read or open other
ALL-UNNAMED
in the command line
- API:
- Because it does not have a name, it cannot be referenced in module-info.java.
- Readable only by automatic modules
Resources
Disclaimer: I was a manuscript reviewer of this book and, thus, got a free copy of it from Manning.
Acknowledgments
- Special thanks to Vera Zvereva whose feedback was invaluable.
- https://github.com/ajaxorg/ace/pull/3628