To JAR Hell And Back

A Life Migration To Java 9

Public Service Announcement

Java Platform Module System

Delaying Java since 2008!

JAR Hell is bad, mkay?

JARs have:

  • no name the JVM cares about

  • no explicit dependencies

  • no well-defined API

  • no concept of versions

Some consequences:

  • NoClassDefFoundError

  • no encapsulation across JARs

  • version conflicts

Modules

Modules are like JARs but have:

  • proper names

  • explicit dependencies

  • a well-defined API

  • no concept of versions 😭

Important goals:

  • reliable configuration

  • strong encapsulation

Module Declaration

A file module-info.java:

module java.sql {
	requires transitive java.logging
	requires transitive java.xml
	uses java.sql.Driver
	exports java.sql
	exports javax.sql
	exports javax.transaction.xa
}

Reliable Configuration

module java.sql {
	requires transitive java.logging
	requires transitive java.xml
}

Module system enforces:

  • all required modules are present

  • no ambiguity

  • no static dependency cycles

  • no split packages

Strong Encapsulation

module java.sql {
	exports java.sql
	exports javax.sql
	exports javax.transaction.xa
}

Say you want to access java.sql.ResultSet.

Module system only grants access if:

  • ResultSet is public

  • java.sql is exported by java.sql

  • your module reads java.sql

Other Features

  • decoupling via services

  • finer grained dependencies and exports

  • open packages and modules (for reflection)

  • unnamed and automatic modules (for migration)

  • layers (for containers)

  • jlink to create runtime images

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.

Migration Challenges

What to look out for
when running on JDK 9

Break Stuff

Some internal changes break existing code!

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

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
    --permit-illegal-access

Java EE 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 are

  • deprecated for removal

  • not resolved by default

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?

  • pick a third-party implementation

  • add Java EE platform modules
    with --add-modules

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

  • Extension Mechanism

  • Endorsed Standards Override Mechanism

  • Boot Class Path Override

  • JRE version selection with -version:N

But wait, there’s more!

Yes, yes, there’s more:

Java 9 Migration Guide
(tiny.cc/java-9-migration)

Background:

And there are new version strings:

  • goodbye 1.9.0_31, hello 9.1.4
    (soon 18.3?)

General Advice I

The most relevant for most applications:

  • internal APIs

  • split packages

  • Java EE 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 9 and try it!

Modularizing Your Project

Making Use Of The Module System

The Tale Of Two Paths

Class path is for regular JARs,
module path is for modular JARs?

Wrong!

Class path is for the unnamed module,
module path is for named modules!

From The Bottom Up

You can put modular JARs in the class path
and everything works like before.

  • you can modularize projects,
    without clients even realizing

  • start with the modularization
    from the bottom up

From The Top Down

What if you want to modularize your code
but your dependencies aren’t yet?

  • proper modules can not depend on
    "the chaos on the class path"

  • this is not possible:

    module monitor.rest {
    	// we need spark.core
    	requires unnamed;
    }

Automatic Modules

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

Automatic Module Example

  • put spark-core-2.7.0.jar on the module path

  • then this works:

    module monitor.rest {
    	requires spark.core;
    }

What Goes Where?

Class PathModule Path

Regular JAR

Unnamed Module

Automatic Module

Modular JAR

Unnamed Module

Named Module

Full Picture

If you want more details:

Java 9 Migration Guide
(tiny.cc/java-9-migration)

For the the full picture get my book:

The Java 9 Module System
(tiny.cc/jms)

About Nicolai Parlog

37% off with
code fccparlog

tiny.cc/jms

Want More?

⇜ Get my book!

You can hire me:

  • training (Java 8/9, JUnit 5)

  • consulting (Java 8/9)

Image Credits