Java Modules In Real Life

Developer Advocate

Java Team at Oracle

Java Modules In Real Life

Why use modules?
Incremental modularization
What are common or tricky roadblocks?
Where's the exit?!
When (not) to use modules?

Java Modules In Real Life

Why use modules?
Incremental modularization
What are common or tricky roadblocks?
Where’s the exit?!
When (not) to use modules?

Managing dependencies

Does this compile
(when built with Maven)?

<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>31.1-jre</version>
</dependency>
import org.checkerframework.checker.units.qual.Prefix;
public class Transitive {
	private final Prefix prefix = Prefix.centi;
}

Should it?

Managing dependencies

Now it doesn’t
(regardless of build tool):

module com.example.main {
	requires com.google.common;
}

⇝ Modules make dependencies more explicit.

Self-contained applications

Explicit dependencies in action:

# create the image
$ jlink
    --module-path mods
    --add-modules com.example.main
    --output app-image

# list contained modules
$ app-image/bin/java --list-modules
> com.example.app
# other app modules
> java.base
# other java/jdk modules

⇝ Modules make it easy to create application images.

Defining APIs

Which packages can I use?

junit pioneer packages

Defining APIs

Exactly these:

module org.junitpioneer {
	exports org.junitpioneer.jupiter;
	exports org.junitpioneer.jupiter.cartesian;
	exports org.junitpioneer.jupiter.params;
	exports org.junitpioneer.vintage;
}

⇝ Modules clarify APIs.

⇝ Modules add module-internal accessibility.
(enforced by compiler and runtime).

Project-internal APIs

How to stop projects integrating your code
from using all your APIs?

module com.example.lib {
	exports com.example.lib.internal
		to com.example.main;
}

⇝ Modules add project-internal accessibility.
(enforced by compiler and runtime).

Illegal access errors

  • in Spring LDAP:

    org.springframework.ldap...AbstractContextSource
    ⇝ com.sun.jndi.ldap.LdapCtxFactory
  • in Error Prone:

    com.google.errorprone.BaseErrorProneJavaCompiler
    ⇝ com.sun.tools.javac.api.BasicJavacTask
  • in veraPDF-library:

    org.verapdf.gf.model.impl.external.GFPKCSDataObject
    ⇝ sun.security.pkcs.PKCS7

Illegal access

Dependencies on internals of…​

  • JDK

  • frameworks

  • libraries

... are a risk.

⇝ Modules make these explicit
(with errors or --add-exports)
and incentivize fixing them.

Explicit services

Which services does a JAR use?

module java.sql {
	uses java.sql.Driver;
}

⇝ Modules make consuming services explicit.

Simple services

No more files in META-INF/services/ — instead:

module com.example.sql {
	provides java.sql.Driver
		with com.example.sql.ExampleDriver;
}

⇝ Modules make providing services simpler.

New abstraction

What describes a project
(and how to look it up):

  • name ⇝ build tool

  • API ⇝ 🤷🏾‍♂️

  • dependencies ⇝ build tool

  • services ⇝ META-INF/services/

  • high-level documentation ⇝ 🤷🏼‍♀️

⇝ Modules express this in one file.

High-level documentation

Module declaration is a great place to document:

  • central abstraction, contract, design

  • unexpected dependencies

  • unusual API

  • allowance of reflective access

  • service interactions

Evolving architecture

Module declarations:

  • define and document a project

  • are verified by compiler and runtime

  • can be evaluated by other tools

  • are obvious to review

⇝ Modules are a living representation
of a project’s architecture.

Java modules in real life

Why use modules?
Incremental modularization
What are common or tricky roadblocks?
Where’s the exit?!
When (not) to use modules?

Incremental modularization

Why consider it?

  • partial modularization gives you
    partial benefits

  • can avoid some roadblocks

  • makes the process more relaxed

Incremental modularization

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

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.

⇝ Why the class path "just works".

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 com.example.app {
    	requires unnamed;
    }

Automatic modules

One 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

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

Recommendations

In order of precedence:

  1. no unmodularized dependencies ⇝ bottom up

  2. bottom up or top down

  3. roadblocks ⇝ continue elsewhere (inside out)

Remember, partial modularization brings partial benefits!

Java Modules In Real Life

Why use modules?
Incremental modularization
What are common or tricky roadblocks?
Where’s the exit?!
When (not) to use modules?

Roadblocks

Before we look at specific situations:

  • most problems originate in dependencies

  • often stem from automatic modules

  • can often be fixed by demoting them to class path

Automatic Culprits

Many problems come from JARs on the module path
that aren’t ready to be modules.

Minimize number of automatic modules!

Only put on module path:

  • your modular JARs

  • the JARs required by modular
    JARs on the module path

That deals with most transitive dependencies.

Automatic Culprits

If your code directly depends on
a troublesome automatic module:

  • put problematic JARs on class path

  • subprojects that depend on them:

    • do not modularize

    • define automatic module name

    • put on module path

⇝ Modularize elsewhere.

Roadblocks

Some common or tricky roadblocks:

  • split packages

  • broken module descriptors

  • reflective access

  • unsupported media type

More details on GitHub:
nipafx/module-system-woes

Unsupported media type

Projects that aren’t prepared for modules:

  • can have various run-time issues

  • sometimes react poorly by
    hiding the underlying cause

⇝ Search the log for module-related errors.

Searching the log

Search terms for module system errors:

  • "module", "lang.module", "module path"

  • "layer", "boot layer"

  • "visible", "exported", "public", "illegal", "access"

Sometimes, projects just swallow errors. 😔

⇝ Take the module system out of the equation.

Suspending modules

Everything* that works on the module path also works on the class path.

(* except services in module-info.java)

When debugging a weird error:

Healing the world

Two categories of problems in dependencies:

  • they do something they shouldn’t

  • they don’t tell you that
    you need to do something

Such cases need to be fixed on their end!

⇝ Makes the Java ecosystem more reliable for everybody.

Java Modules In Real Life

Why use modules?
Incremental modularization
What are common or tricky roadblocks?
Where’s the exit?!
When (not) to use modules?

Getting out

If roadblocks are insurmountable,
getting out is easy:

find . -name module-info.java -type f -delete

Preparations

But first:

  • move documentation elsewhere

  • declare services the old way

  • update command line options
    (e.g. --add-exports and paths)

  • move from jlink to jpackage

Lost work

What about all the work?!

Some benefits remain:

  • good while it lasted

  • cleaner architecture

  • documentation

Getting out

Not a great step,
but at least it’s possible!

⇝ Makes it feasible to experiment.

Java Modules In Real Life

Why use modules?
Incremental modularization
What are common or tricky roadblocks?
Where’s the exit?!
When (not) to use modules?

Observations & Assumptions

Observations:

  • modules require & enforce a decent architecture

  • (re)factoring modules << (re)factoring code

  • most problems originate in dependencies

Assumption:

  • barring problematic dependencies,
    modules are a net-positive

Those damn dependencies!

Why are dependencies so often the problem?

  • almost all still baseline against JDK ≤ 8

  • makes creating/maintaining modules tougher

  • number of modularized apps is small
    and thus demand is small (but growing)

But more and more ship as modules:
sormuras/modules tracks 4413 unique modules
(as of September 30th, 2022).

Project size

What impact does project size have?

Simplified:

SizeEffortBenefit

small

📉

📉

large

📈

📈

⇝ Project size doesn’t matter (much).

Project type

General benefits for all kinds of projects:

  • managing dependencies

  • preventing (accidental) use
    of internal (JDK) APIs

  • new abstraction

  • etc.

Project type

Frameworks and libraries:
  • benefit from strong encapsulation

  • shouldn’t limit users

Applications benefit from:
  • strong encapsulation (!)

  • self-contained images (possibly)

⇝ Project type has little practical impact,
but reused code should consider users.

Project age

New projects:
  • module declarations and architecture
    evolve side by side

  • new dependencies can be vetted

Existing projects:
  • modules must be retrofitted

  • architecture may require updating
    (which can be the goal!)

  • existing dependencies must work

⇝ New projects are a better fit.

Domain (knowledge)

Impact of domain (knowledge) on dependencies:

  • maintenance of domain-specific dependencies?

  • choice of domain-specific dependencies?

  • understanding of domain to evaluate 👆🏾
    and to analyze unexpected problems

⇝ Easier if domain isn’t niche
and you understand it well.

When (not)?

Best place to start:

  • new projects

  • in well-understood,
    well-maintained domain

  • particularly if reused

Next best place: your current project!
(Unless it has lots of outdated dependencies.)

How to

Do:
  • Accept that partial modularization brings
    partial benefits. Then start small.

  • Use module declarations to analyze, guide,
    document, and review architecture.

Don’t:
  • Get stuck trying to fix dependencies:
    identify root cause, open an issue,
    put on class path, wait for (or contribute) fix.

  • Forget that modules are a seat belt, not a rocket.

Everything will be okay in the end. If it’s not okay, it’s not the end.

— John Lennon

So long…​

37% off with
code fccparlog

bit.ly/the-jms

More

Slides at slides.nipafx.dev
⇜ Get my book!

Follow Nicolai

nipafx.dev
/nipafx

Follow Java

inside.java // dev.java
/java    //    /openjdk

Image Credits