Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

A JPMS Migration Guide for the Impatient

DZone's Guide to

A JPMS Migration Guide for the Impatient

Project Jigsaw is here, or at least it will be with Java 9. If you can't wait to modularize your app, this guide will touch on working with Jigsaw and migrating to it.

· Java Zone
Free Resource

Learn how to troubleshoot and diagnose some of the most common performance issues in Java today. Brought to you in partnership with AppDynamics.

Status of JPMS

After a long period of uncertainty, the JPMS (Project Jigsaw) will finally be part of Java SE 9. Oracle postponed the release date to July 27, 2017. Corresponding to the JDK 9 schedule, JDK is now feature extension complete, and ramp down phase 1 has been started since January 5, 2017. However, there are still several issues not yet implemented, such as #IndirectQulifiedReflectiveAccess and #ReflectiveAccessToNonExportedTypes.

Summary

This article discusses some recent changes of the module declaration in the Java language, reveals a migration path for existing projects, and shows how to convert them into modules, as well as it highlighting some known changes in JDK 9 caused by the implementation of JPMS.

I assume that the reader of this article is aware of the main concepts of JPMS. Nevertheless, let's walk through the most recent changes and enhancements so that it is easier to follow up on my explanations.

Latest Important Enhancements

Mark Reinhold, chief architect of the Java Platform Group at Oracle, made a proposal on October 27, 2016, for the issue #ReflectiveAccessToNonExportedTypes. Thus, the module declaration in the Java language was enhanced to support the open(s) keyword for modules and packages at December 19, 2016.

The Open(s) Keyword

If a module or a package is opened, then all its content is accessible at runtime via reflection libraries of Java SE Platform.

Before this change, reflection was only allowed on the public parts of exported types – which somehow was useless.

// open the whole module to all other modules
open module A {
  exports B;
  requires C;
}

// open package E to all other modules and package F to package G
module D {
  opens E;
  opens F to G;
}


We can even restrict to whom we want to give explicit access. For example we can open a specific package of our module to be accessed via reflection only by JPA:

module foo.model {
  requires java.persistence;
  opens com.foo.model to java.persistence;
}


The open(s) declaration only opens the declared package for reflective access at runtime. Reading access at compile time still needs a requires declaration.

The Transitive Keyword

In the same proposal as the keyword open(s) is specified, the keyword public is renamed to transitive.

module A {
  requires transitive B;
}


The requires transitive declaration in module A for package B means that any module that requires module A also gets read access to package B.

Modular JDK

The whole JDK has been migrated to consist of modules (JEP 200: Modular JDK). The two main goals of the JPMS are

  • Reliable configuration, to replace the brittle, error-prone class-path mechanism with a means for program components to declare explicit dependences upon one another.

  • Strong encapsulation, to allow a component to declare which of its APIs are accessible by other components, and which are not.

Reliable Configuration

To implement reliable configuration, the class path will be replaced with the module path in the long run. Now, with the release of Java 9, both the class path and the module path will coexist.

To locate modules defined in artifacts, the module system searches the module path, which is defined by the host system. In contrast to the former class path, the module path is more robust because the compiler or the virtual machine reports an error if:

  • A module in the module path is not found on the host.

  • Any requires clause in a module declaration cannot be satisfied.

  • A module is more than once on the module path.

  • A package is in more than one module defined.

That implies that if an application only consists of modules and all of them are on the module path:

  • Any package (and therefore any type) is at most once defined on the module path. The order of the modules on the module path has no relevance.

  • The likelihood that a required class is not found at run time has decreased because the compiler or the virtual machine can detect unsatisfied requirements now at compile time and throw an error in case.

Strong Encapsulation

The discussion of strong encapsulation is kept short because the reader is assumed to be aware of the main concepts to JPMS.

Visibility of public items in a module is limited to the declaring module, unless otherwise declared as exported in the module declaration.

Even when a package is exported by a module A, module B is only allowed to read it if it declares a requirement to given package.

//Declaration of module A
module A {
  exports org.foo.bar;
}

//Declaration of module B
module B {
  requires org.foo.bar;
}


Module B is allowed to read public parts of package org.foo.bar of module A at compile time and at run time.

Migration of Existing Code to JPMS

The Unnamed Module

If a request is made to load a type whose package is not defined in any known module, then the module system will attempt to load it from the class path. If this succeeds, then the type is a member of a special module known as the unnamed module to ensure that every type is associated with some module.

The unnamed module reads every other module. Code in any type loaded from the class path will thus be able to access the exported types of all other readable modules, which by default will include all named, built-in platform modules.

This ensures that any Java application that runs under Java SE 8 will continue to run under Java SE 9 (as long it only uses standard, non-deprecated Java SE APIs).

Named modules are not allowed to declare dependences on the unnamed module (or to inside packages) and all their content is inaccessible for them. Furthermore, if a package is already declared in a named module, any equally named packages in the unnamed module are ignored.

Knowing these facts, we are now able to migrate an application consisting of many artifacts in a bottom-up way. In each artifact, we add a module declaration to the source code, we compile it, we package the result as a modular JAR file, and add it to the module path.

Suppose we have the case of an application consisting of app.jar and lib.jar:

Image title

Step 1: We leave the JAR files as they are and put them on the class path. The application will then run because the two JAR files are loaded as part of the unnamed module. They have access to all platform modules of Java 9.

Step 2: We refactor lib.jar to be a module (by adding a module declaration to it) and put it to the module path.

Step 3: We also refactor app.jar to be a module and put it together with lib.jar on the module path.

But what if we depend on an artifact from an external company and what if the artifact is not yet a module — or maybe never will be a module?

Automatic Modules

We can place any JAR file, even without a module declaration, on the module path. Its module name will be automatically derived from its filename, and other modules can declare dependences upon it.

An automatic module is made to read any other module, whether automatic or explici,t and it is also made to export every package it contains.

Going back to our previous example, our lib.jar now depends upon on ext-lib.jar.

Image title


Step 1: We place the ext-lib.jar on the module path as an automatic module. No changes to the ext-lib.jar are needed.

We can now refactor our two JAR files (app.jar and lib.jar) in the same manner as we did before in step 2 and step 3.

On top, an automatic module is made to read the unnamed module, but not exporting any packages which contain types from the unnamed module. With this bridge from the code in explicit modules and code on the class path, it is possible to migrate applications with dependences to JAR files that contain even the same package in more than one file.

Services

The module declaration supports the declaration of services and its implementations — the Service Provider Interface (SPI) pattern. The following example demonstrates the use of a java.sql.Driver which, in this case, is provided by an external module (MySQL).

My module com.foo declares the use of a java.sql.Driver implementation

module com.foo {
  requires java.sql;
  uses java.sql.Driver
}


And the module com.mysql.jdbc declares the implementation of the java.sql.Driver interface by the class com.mysql.jdbc.Driver

module com.mysql.jdbc {
  requires java.sql;
  exports com.mysql.jdbc;
  provides java.sql.Driver with com.mysql.jdbc.Driver;
}


Now inside the module com.foo, we can access the implementation of the service as usual.

private java.sql.Driver getDriver(){
  Iterator<Driver> it = ServiceLoader.load(Driver.class).iterator();
  if(it.hasNext()) return it.next();
  throw new RuntimeException(“No Driver available”);
}


With this kind of declaration, the existence of the service defining files (the file named java.sql.Driver with the text “com.mysql.jdbc.Driver”) under META-INF/services in the JAR file is no longer needed, but if the module is an automatic module, these service files are still respected.

Encapsulation of Internal APIs

The internal APIs of JDK, such as sun.* packages, are not part of the public API and most of the them are no longer accessible by default (JEP 260: Encapsulate Most Internal APIs). They are divided into two broad categories

  • Non-critical: Those which do not appear to be used by code outside of the JDK, or are used by outside code merely for convenience, i.e., for functionality that is available in supported APIs or that can easily be provided by libraries (e.g., sun.misc.BASE64Decoder).

  • Critical: Those which provide critical functionality that would be difficult, if not impossible, to implement outside of the JDK itself (e.g., sun.misc.Unsafe).

In the first step, everything in the non-critical category and everything in the critical category, for which a supported replacement exists in Java 8, will be encapsulated by default (will no longer be visible from outside). As a last resort to access to these APIs, there is a command line flag at compile time and at run time.

In Java 9, there are some enhancements that provide replacements for critical parts of the internal APIs. Those critical parts are marked as deprecated and either will be encapsulated or removed in Java 10.

The critical internal APIs proposed to remain accessible in JDK 9 are:

  • sun.misc.{Signal,SignalHandler}.

  • sun.misc.Unsafe (The functionality of many of the methods in this class is now available via variable handles, JEP 193).

  • sun.reflect.Reflection::getCallerClass(int) (The functionality of this method may be provided in a standard form via JEP 259).

  • sun.reflect.ReflectionFactory.newConstructorForSerialization.

The above critical internal APIs will be placed in a JDK-specific module named jdk.unsupported.

Further Changes Caused by JPMS

Here is the incomplete list of changes caused by the implementation of modules in Java 9 (JEP 261: Module System):

  • The ClassLoader::getResource* and Class::getResource* methods can no longer be used to read JDK-internal resources. Module-private resources can be read via the Module::getResourceAsStream method or, alternatively, via the jrt: URL scheme and filesystem defined in JEP 220.

  • The java.lang.reflect.AccessibleObject::setAccessible method cannot be used to gain access to members of packages that are not exported by their defining modules; an InaccessibleObjectException will be thrown. The package must be opened to the accessing module.

  • The application and platform class loaders are no longer instances of the java.net.URLClassLoader class.

  • The META-INF/services resource files previously found in rt.jar and other internal artifacts are not present in the corresponding system modules, since service providers and dependences are now declared in module descriptors.

Conclusion

There are no show-stoppers for the migration of existing code into modules anymore. Even the problem with reflective access to non-exported types has been solved. Since we have the unnamed module and the automatic modules, smooth migration of our applications is now feasible. At least if our application does not use internal API of the JDK.

This article shows the main steps to achieve a successful migration and has listed the key points of potential problems.

Most of our business applications run in a JEE server. That is why we still should wait taking actions until JEE servers support Java 9.

Understand the needs and benefits around implementing the right monitoring solution for a growing containerized market. Brought to you in partnership with AppDynamics.

Topics:
java 9 module ,java ,tutorial ,code migration ,project jigsaw

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}