module A {
requires B;
}
there’s much to talk about
⇝ this can only cover parts of the JPMS
⇝ we have to skip some details
slides at slides.codefx.org/jpms
Java Module System Basics |
Beyond The Basics |
Command Line Options |
Migration Challenges |
Incremental Modularization |
Modules
have a unique name
express their dependencies
export specific packages
and hide the rest
These information
are defined in module-info.java
get compiled to module-info.class
end up in JAR root folder
Modules express dependencies
with requires
directives:
module A {
requires B;
}
module system checks all dependencies
(⇝ reliable configuration)
lets module read its dependencies
Modules export packages
with exports
directives
module B {
exports p;
}
Code in module A can only access Type
in module B if:
Type
is public
Type
is in an exported package
A reads B
(⇝ strong encapsulation)
public static void main(String[] args) {
List<SurpriseFactory> factories = List.of(
new ChocolateFactory(), new QuoteFactory());
Calendar cal = Calendar.create(factories);
System.out.println(cal.asText());
}
module surprise {
// requires no other modules
exports org.codefx.advent.surprise;
}
module calendar {
requires surprise;
exports org.codefx.advent.calendar;
}
module factories {
requires surprise;
exports org.codefx.advent.factories;
}
module advent {
requires calendar;
requires factories;
requires surprise;
}
# compile with module-info.java
$ javac -d classes ${*.java}
# package with module-info.class
# and specify main class
$ jar --create
--file mods/advent.jar
--main-class advent.Main
${*.class}
# run by specifying a module path
# and a module to run (by name)
$ java --module-path mods --module advent
Transitive Dependencies |
Optional Dependencies |
Services |
Qualified Exports |
Reflective Access |
Transitive Dependencies |
Optional Dependencies |
Services |
Qualified Exports |
Reflective Access |
So far, exported packages are visible
to every reading module.
What if a set of modules wants to share code?
Similar to utility classes:
if class should be visible outside of package,
it has to be public ⇝ visible to everybody
if package should be visible outside of module,
it has to be exported ⇝ visible to everybody
Module system fixes the former.
What about the latter?
module A {
exports some.pack to B;
}
B can access some.pack
as if regularly exported
other modules can not access it
as if not exported at all
To ease implementation of SurpriseFactory
:
create new module factory
add class AbstractSurpriseFactory
export that package only to
factory implementation modules
module factory {
requires transitive surprise;
exports factory
to factory.chocolate, factory.quote;
}
With A exports pack to B
:
only B can access types in some.pack
other modules behave as if some.pack
is not exported
Use to share sensible code between modules.
Transitive Dependencies |
Optional Dependencies |
Services |
Qualified Exports |
Reflective Access |
Exporting a package makes it public API:
modules can compile code against it
clients expect it to be stable
What if a type is only meant
to be accessed via reflection?
(Think Spring, Hibernate, etc.)
module A {
opens some.pack;
}
at compile time:
types in some.pack
are not accessible
at run time:
all types and members in some.pack
are accessible
A qualified variant (opens to
) exists.
open module A {
// no more `opens` directives
}
The same as open packages
but for all of them!
With open modules or open packages:
code can be made accessible
at run time only
particularly valuable to open
for reflection
Use to make types available for reflection
without making them public API.
The module system is pretty strict but…
… not all modules are well-designed
… not all use cases were intended
What to do then?
Command line options to the rescue!
(I often call them "escape hatches".)
All command line flags can be applied
to javac
and java
!
When used during compilation,
they do not change the resulting
module descriptor!
If a module is not required,
it might not make it into the module graph.
Help it get there with --add-modules
:
$ java --module-path mods
# --add-modules <module>(,<module>)*
--add-modules java.xml.ws.annotation
--module advent
Maybe a module in the graph is not readable
by another but you need it to be.
Add readability edges with --add-reads
:
$ java --module-path mods
# --add-reads <module>=<target>(,<more>)*
--add-reads advent=factories
--module advent
A common case:
A module needs types that
the owning module doesn’t export.
Export them with --add-exports
:
$ java --module-path mods
# --add-exports <module>/<package>=<target>
--add-exports factories/factory.quotes=advent
--module advent
Use target ALL-UNNAMED
to export
to code on the class path.
Another common case:
A module reflects on types from a package that
the owning module doesn’t open.
Open packages with add-opens
:
$ java --module-path mods
# --add-opens <module>/<package>=<target>
--add-opens factories/factory.quotes=advent
--module advent
Use target ALL-UNNAMED
to open
to code on the class path.
(It is not possible to open an entire module.)
Maybe you have a package split…
Mend it with --patch-module
:
$ java --module-path mods
--add-modules java.xml.ws.annotation
# --patch-module <module>=<JAR>
--patch-module java.xml.ws.annotation=jsr305.jar
--module advent
All classes from jsr305.jar
are put
into java.xml.ws.annotation.
By putting JAR content into a module A:
split packages can be mended
A needs to read JAR’s dependencies,
which need to export used packages
modules using JAR content need to read A
and A needs to export used packages
Often used with --add-reads
and --add-exports
.
Edit module graph with:
--add-modules
to add modules
--add-reads
to add readability edges
--patch-module
to add classes to module
--add-exports
to export packages to modules
--add-opens
to open packages to modules
The latter two accept ALL-UNNAMED
as target.
More at codefx.org:
Some internal changes break existing code!
Just by running on JDK 11
(even without modularizing the application).
Modularized JDK and legacy JARs have to cooperate.
Two requirements:
for the module system to work,
everything needs to be a module
for compatibility, the class path
and regular JARs have to keep working
The Unnamed Module
contains all JARs on the class path
(including modular JARs).
has no name (surprise!)
can read all modules
exports all packages
Inside the unnamed module
"the chaos of the class path" lives on.
internal APIs
JEE modules
split packages
runtime images
internal APIs are:
all in sun.*
most in com.sun.*
(unless marked @jdk.Exported
)
encapsulated at compile time
accessible at run time
for some time
critical APIs may survive longer
(e.g. sun.misc.Unsafe
)
JDeps can report internal dependencies:
$ jdeps --jdk-internals
-recursive --class-path 'libs/*'
scaffold-hunter-2.6.3.jar
> batik-codec.jar -> JDK removed internal API
> JPEGImageWriter -> JPEGCodec
> guava-18.0.jar -> jdk.unsupported
> Striped64 -> Unsafe
> scaffold-hunter-2.6.3.jar -> java.desktop
> SteppedComboBox -> WindowsComboBoxUI
look for reflection, especially
Class::forName
AccessibleObject::setAccessible
recursively check your dependencies!
fix your code
contact library developers
look for alternatives
(in the JDK or other libraries)
consider command line flags
--add-exports
, --add-opens
, or
--illegal-access
java.activation (javax.activation
)
java.corba (CORBA packages)
java.transaction (javax.transaction
)
java.xml.bind (javax.xml.bind.*
)
java.xml.ws (JAX-WS packages)
java.xml.ws.annotation (javax.annotation
)
These were
deprecated for removal in ⑨
removed in ⑪
JDeps shows dependencies on platform modules:
$ jdeps -summary sh-2.6.3.jar
> sh-2.6.3.jar -> java.base
> sh-2.6.3.jar -> java.datatransfer
> sh-2.6.3.jar -> java.desktop
> sh-2.6.3.jar -> java.logging
> sh-2.6.3.jar -> java.prefs
> sh-2.6.3.jar -> java.sql
> sh-2.6.3.jar -> java.xml
packages should have a unique origin
no module must read the same package
from two modules
The implementation is even stricter:
no two modules must contain
the same package (exported or not)
split packages on class path
are inaccessible
some libraries split java.xml.*
, e.g. xml-apis
some JBoss modules split, e.g.,
java.transaction
, java.xml.ws
jsr305 splits javax.annotation
JDeps reports split packages:
$ jdeps -summary
-recursive --class-path 'libs/*'
project.jar
> split package: javax.annotation
> [jrt:/java.xml.ws.annotation,
> libs/jsr305-3.0.2.jar]
Your artifacts:
rename one of the packages
merge package into the same artifact
merge the artifacts
place both artifacts on the class path
Otherwise:
upgrade the JDK module with the artifact
--patch-module
with the artifact’s content
new JDK/JRE layout
internal JARs are gone (e.g. rt.jar
, tools.jar
)
JARs are now JMODs
application class loader is no URLClassLoader
(no way to append to its class path)
new URL schema for run-time image content
does the code rummage around
in the JDK / JRE folder?
are URLs to JDK classes / resources handcrafted?
search for casts to URLClassLoader
Compact Profiles — jlink
Endorsed Standards Override Mechanism,
Extension Mechanism,
Boot Class Path Override — --upgrade-module-path
JRE selection -version:N
— jlink
?
Web Start — openwebstart.com
JavaFX — openjfx.io/
Yes, yes, there’s more:
Background:
And there are new version strings:
goodbye 1.9.0_31
, hello 9.0.1
The most relevant for most applications:
internal APIs
split packages
JEE modules
get your code in shape
(and prevent relapses)
check your dependencies and tools
if any are suspicious
(automatically true for IDEs, build tools):
make sure they’re alive
get them up to date!
or look for alternatives
download Java 11 and try it!
most module systems are "in or out",
but modularized JDK and legacy JARs
have to cooperate!
there is a boundary between
legacy and modules
Incremental modularization means
moving that boundary.
Incremental modularization is enabled by two features:
Unnamed Module(s)
Automatic Modules
And the fact that module and class path coexist:
modular JARs can be put on either
"regular" JARs can be put on either
The Unnamed Module
contains all JARs on the class path
(including modular JARs).
has no name (surprise!)
can read all modules
exports all packages
Inside the unnamed module
"the chaos of the class path" lives on.
Put all your JARs on the class path.
what if your code was modularized
and your dependencies were not?
proper modules can not depend on
"the chaos on the class path"
this is not possible:
module calendar {
requires unnamed;
}
An Automatic Module
is created for each "regular" JAR
on the module path.
name defined by manifest entry
AUTOMATIC-MODULE-NAME
or
derived from JAR name
can read all modules
(including the Unnamed Module)
exports all packages
put guava-19.0.jar
on the module path
then this works:
module calendar {
requires guava;
}
Class Path | Module Path | |
---|---|---|
Regular JAR | Unnamed Module | Automatic Module |
Modular JAR | Unnamed Module | Explicit Module |
Unnamed or named module?
The user decides, not the maintainer!
Three strategies emerge:
bottom-up
top-down
inside-out
Works best for projects without
unmodularized dependencies
(libraries).
turn project JARs into modules
they still work on the class path
clients can move them to the module path
whenever they want
Required for projects with
unmodularized dependencies
(applications).
turn project JARs into modules
modularized dependencies:
require direct ones
put all on the module path
unmodularized dependencies:
require direct ones with automatic name
put direct ones on the module path
put others on the class path
When dependencies get modularized:
hopefully the name didn’t change
if they are already on the module path,
nothing changes
otherwise move them there
check their dependencies
What about published projects with
unmodularized dependencies
(libraries)?
top-down mostly works
but there’s an important detail
about automatic module names!
automatic module name may
be based on JAR name
file names can differ
across build environments
module name can change
when project gets modularized
⇝ Such automatic module names are unstable.
dependencies might require the same
module by different names
the module system does not support that
there is no way to launch that application!
⇝ Do not publish modules
that depend on automatic modules
whose names are based on file names!
thanks to manifest entry,
projects can publish their module name
assumption is that it won’t change
when project gets modularized
that makes these names stable
⇝ It is ok to publish modules
that depend on automatic modules
whose names are based on manifest entry.
💻 codefx.org
🐦 @nipafx
Slides at slides.codefx.org
⇜ Get my book!
You can hire me:
training (Java 8-14, JUnit 5)
consulting (Java 8-14)
puzzle-people: Kevin Dooley (CC-BY 2.0)
binary-code: Christiaan Colen (CC-BY-SA 2.0)
ball-of-mud-2: Andi Gentsch (CC-BY-SA 2.0)
jar-hell: Wellcome Library, London (CC-BY 4.0)
flag-amsterdam: George Rex (CC-BY-SA 2.0)
puzzle-cubed: David Singleton (CC-BY 2.0)
puzzle-piece-green:
StockMonkeys.com
(CC-BY 2.0)
puzzle-pieces-put-together:
Ken Teegardin
(CC-BY-SA 2.0)
iceberg:
NOAA’s National Ocean Service
(CC-BY 2.0)
class and module diagrams:
Nicolai Parlog
(CC-BY-NC 4.0)
keep-out: Brian Smithson (CC-BY 2.0)
garbage-only: Peter Kaminski (CC-BY 2.0)
golden-gate: Nicolas Raymond (CC-BY 2.0)
confusion: Procsilas Moscas (CC-BY 2.0)
module diagrams:
Nicolai Parlog
(CC-BY-NC 4.0)
broken-glass:
Eric Schmuttenmaer
(CC-BY-SA 2.0)
internals: David French (CC-BY 2.0)
cut: Jinx! (CC-BY-SA 2.0)
cells: Jonathan Lin (CC-BY-SA 2.0)
obsolete: Trevor King (CC-BY 2.0)
sign: Duncan Harris (CC-BY-SA 2.0)
question-mark: Milos Milosevic (CC-BY 2.0)
bundles: Danumurthi Mahendra (CC-BY 2.0)