The Java Module System

Let’s get started!

  • 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

Table of contents

Java Module System Basics
Beyond The Basics
Command Line Options
Migration Challenges
Incremental Modularization

Java Module System Basics

The very short version

Modules

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

Readability

Modules express dependencies
with requires directives:

module A {
	requires B;
}
  • module system checks all dependencies
    (⇝ reliable configuration)

  • lets module read its dependencies

Accessibility

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)

Jigsaw Advent Calendar

A simple example

Structure

advent calendar structure

Code

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 Structure

b2e21fbf

Module Structure

advent calendar module multi
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;
}

Module Creation

Compilation, Packaging, Execution

# 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

Java Module System Beyond The Basics

Transitive Dependencies
Optional Dependencies
Services
Qualified Exports
Reflective Access

Java Module System Beyond The Basics

Transitive Dependencies
Optional Dependencies
Services
Qualified Exports
Reflective Access

Qualified Exports

So far, exported packages are visible
to every reading module.

What if a set of modules wants to share code?

Known Problem

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?

Qualified Exports

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

Factory Utilities

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;
}

Summary

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.

Java Module System Beyond The Basics

Transitive Dependencies
Optional Dependencies
Services
Qualified Exports
Reflective Access

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.)

Open Packages

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 Modules

open module A {
	// no more `opens` directives
}

The same as open packages
but for all of them!

Summary

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.

Command Line Options

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".)

Command Line Options

All command line flags can be applied
to javac and java!

When used during compilation,
they do not change the resulting
module descriptor!

Add Modules

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

Add Readability Edges

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

Add Exports

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.

Open Packages

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.)

Patch Modules

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.

Patch Module

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.

Summary

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:

Migration Challenges

What to look out for
when running on JDK 11

Break Stuff

Some internal changes break existing code!

Just by running on JDK 11
(even without modularizing the application).

Of Modules And JARs

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

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.

Challenges

  • internal APIs

  • JEE modules

  • split packages

  • runtime images

Internal APIs

  • 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)

What to look for?

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

What else to look for?

  • look for reflection, especially

    • Class::forName

    • AccessibleObject::setAccessible

  • recursively check your dependencies!

What to do?

  1. fix your code

  2. contact library developers

  3. look for alternatives
    (in the JDK or other libraries)

  4. consider command line flags
    --add-exports, --add-opens, or
    --illegal-access

JEE Modules

  • 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 ⑪

What to look for?

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

What to do?

Split Packages

  • 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

Examples

  • some libraries split java.xml.*, e.g. xml-apis

  • some JBoss modules split, e.g.,
    java.transaction, java.xml.ws

  • jsr305 splits javax.annotation

What to look for?

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]

What to do?

Your artifacts:

  1. rename one of the packages

  2. merge package into the same artifact

  3. merge the artifacts

  4. place both artifacts on the class path

Otherwise:

  1. upgrade the JDK module with the artifact

  2. --patch-module with the artifact’s content

Run-Time Images

  • 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

What to look for?

  • does the code rummage around
    in the JDK / JRE folder?

  • are URLs to JDK classes / resources handcrafted?

  • search for casts to URLClassLoader

Obsolete

  • 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/

But wait, there’s more!

Yes, yes, there’s more:

Background:

And there are new version strings:

  • goodbye 1.9.0_31, hello 9.0.1

General Advice I

The most relevant for most applications:

  • internal APIs

  • split packages

  • JEE modules

General Advice II

  • 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!

Incremental Modularization

Moving Into Module Land

Why Is It Even An Option?

  • 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.

Enablers

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

Why The Class Path "Just Works"

Definition

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.

Example

Put all your JARs on the class path.

modularization unnamed

No Access

  • 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;
    }

No Access

modularization unnamed dependency

Automatic Modules

From Modules To The Class Path

Definition

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

Example

  • put guava-19.0.jar on the module path

  • then this works:

    module calendar {
    	requires guava;
    }

Example

modularization automatic

What Goes Where?

Class PathModule Path

Regular JAR

Unnamed Module

Automatic Module

Modular JAR

Unnamed Module

Explicit Module

Unnamed or named module?
The user decides, not the maintainer!

Modularization Strategies

Three strategies emerge:

  • bottom-up

  • top-down

  • inside-out

Bottom-Up

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

Top-Down

Required for projects with
unmodularized dependencies
(applications).

  • turn project JARs into modules

Top-Down

  • 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

Top-Down

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

Inside-Out

What about published projects with
unmodularized dependencies
(libraries)?

  • top-down mostly works

  • but there’s an important detail
    about automatic module names!

Inside-Out

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.

Inside-Out

Impossible Module Requirements

  • 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!

Inside-Out

Manifest Entry

  • 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.

About Nicolai Parlog

37% off with
code fccparlog

tiny.cc/jms

Follow

Want More?

⇜ Get my book!

You can hire me:

  • training (Java 8-14, JUnit 5)

  • consulting (Java 8-14)

Image Credits

Introduction

Project Jigsaw

Java Module System

Incremental Modularization

Migration Challenges

Rest