BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Programming with Modularity and Project Jigsaw. A Tutorial Using the Latest Early Access Build

Programming with Modularity and Project Jigsaw. A Tutorial Using the Latest Early Access Build

Bookmarks

Software can be seen as a system of interacting parts and in Java it is common to package each of those parts in its own JAR. Conceptually a part consists of three properties: a name, a public API for the rest of the world to use, and dependencies on other parts. This graph-like model helps developers and tools dissect, analyse, and work with software systems.

But none of those properties exist inside the Java runtime, which uses the classpath to access a bunch of JARs and simply rolls them all into one big ball of mud. Any distinction between JARs is entirely lost and only a flat set of packages remains, with no capability of further validation. So important questions like “are these all required JARs?”, “are these even the correct JARs?”, “are there conflicts?”, or “are only public APIs used?” cannot be answered by the running JVM.

So on one hand there is a well-structured model of how a system is modularized and how its parts depend on each other. And on the other hand there is the runtime reality of a single, almost entirely unstructured namespace. This mismatch is the driving force behind what is affectionately known as JAR hell, and dependencies on internal APIs. It also causes bad start-up performance and weakens security.

Project Jigsaw will enhance the compiler and runtime to come closer to the structured model. Its main goals are reliable configuration (by declaring dependencies) and strong encapsulation (by hiding internals) and its agent for both is the concept of modules.

Introducing Modules

Quoting from the highly recommended design overview State of the Module System by Oracle chief architect Mark Reinhold:

A module is a named, self-describing collection of code and data. Its code is organized as a set of packages containing types, i.e., Java classes and interfaces; its data includes resources and other kinds of static information.

To control how its code refers to types in other modules, a module declares which other modules it requires in order to be compiled and run. To control how code in other modules refers to types in its packages, a module declares which of those packages it exports.

So in contrast to a JAR, a module has a name that is recognized by the JVM, declares which other modules it depends on, and defines which packages are part of its public API.

Name

Module names can be arbitrary but must not conflict. To that end it is recommended to use the standard inverse-domain-name pattern of packages. While this is not obligatory it will often mean that the module name is a prefix of the packages it contains.

Dependencies & Readability

A module lists the other modules it depends on to compile and run. Again from the “State of the Module System”:

When one module depends directly upon another [...] then code in the first module will be able to refer to types in the second module. We therefore say that the first module reads the second or, equivalently, that the second module is readable by the first.

[…]

The module system ensures that every dependence is fulfilled by precisely one other module, that no two modules read each other, that every module reads at most one module defining a given package, and that modules defining identically-named packages do not interfere with each other.

This concept of readability is the basis of reliable configuration: When any condition is violated, the module system refuses to compile or launch the code; an immense improvement over the brittle classpath model.

Exports & Accessibility

A module lists the packages it exports. A type in one module is only accessible by code in another module if:

  • the type is public
  • the containing package is exported by the first module
  • the second module reads the first

This means that public is no longer really public. A public type in a non-exported package is as hidden from the outside world as a non-public type in an exported package. So "public” is even more hidden than package-private types are today, because the module system does not even permit access via reflection. As Jigsaw is currently implemented, command line flags are the only way around this.

So accessibility builds on top of readability and the export clauses, creating the basis for strong encapsulation, where a module author can clearly express which parts of the module’s API are public and supported.

Example: Creating Our First Module

Say we have an application that monitors microservices running in our network. It periodically contacts them and uses their responses to update a database table as well as a nifty JavaFX UI. For the time being, let’s assume that the application is developed as a single project without any dependencies.

Now let’s switch to Java 9 with Jigsaw! (The early access builds are availale on java.net - the code samples and commands presented in this article were created for build 96 from December 22nd.) While jdeps, the Java dependency analyzer, already exists in JDK 8, we need to use the JDK 9 version so that it knows about modules.

The first thing to note is that we can simply choose to ignore modules. Unless the code is relying on internal APIs or some other JDK implementation details (in which case things will break), the application can be compiled and executed exactly as with Java 8. Simply add the artifact (and its dependencies if it has any) to the classpath and call main. And voilà, it runs!

To move the code into a module we must create a module descriptor for it. This is a source code file called module-info.java in the source directory’s root:

module com.infoq.monitor {
    // add modules our application requires
	// add packages our application exports
}

Our application is now a module with the name com.infoq.monitor. To determine which modules it depends on, we can use jdeps:

jdeps -module ServiceMonitor.jar

This will list the packages our application uses and, more importantly, from which modules they come: java.base, java.logging, java.sql, javafx.base, javafx.controls, javafx.graphics.

With our dependencies covered we can now think about which packages we might export. Since we are talking about a stand-alone application, we won’t export anything.

module com.infoq.monitor {
    requires java.base; // see more about that below
    requires java.logging;
    requires java.sql;
    requires javafx.base;
    requires javafx.controls;
    requires javafx.graphics;
    // no packages to export
}

Compiling is the same as without Jigsaw except we have to include module-info.java in the list of source files:

javac -d classes/com.infoq.monitor ${source files}

Now that all classes were compiled into classes/com.infoq.monitor, we can create a JAR from them:

jar -c \
    --file=mods/com.infoq.monitor.jar \
    --main-class=com.infoq.monitor.Monitor \
    ${compiled class files}

The new --main-class can be used to specify the class containing the application’s entry point. The result is a so-called modular JAR, which we will discuss further below.

In stark contrast to the old model, there is a whole new sequence to launch the application. We use the new -mp switch to specify where to look for modules, and -m to name the one we want to launch:

java -mp mods -m com.infoq.monitor

To understand this a little better, let’s talk about how the compiler and the virtual machine handle modules.

Java And The Modules

Kinds Of Modules

The JDK itself was modularized and consists of about 80 platform modules (have a look at them with java -listmods). The ones that will be standardized for Java 9 SE are prefixed with “java.”, the JDK-specific ones with “jdk.”.

Not all Java environments have to contain all platform modules. On the contrary, one goal of Jigsaw is the scalable platform, which means that it is easy to create a runtime with just the desired modules.

All Java code depends on Object and virtually all code uses basic features like threading and collections. These types can be found in java.base, which plays a special role; it is the only module that the module system inherently knows about, and since all code depends on it, all modules automatically read it.

So in our example above, we would not need to declare a dependency on java.base.

Non-platform modules are called application modules and the module used to launch an application (the one containing main) is the initial module.

Modular JARs

As we have seen above, Jigsaw still creates JARs, albeit with the new semantics. If they contain a module-info.class they are called modular JARs, about which “State of the Module System” says:

A modular JAR file is like an ordinary JAR file in all possible ways, except that it also includes a module-info.class file in its root directory.

Modular JARs can be put on the classpath as well as on the module path (see below). This allows projects to release a single artifact and let its users decide between the legacy or the modular approach to application building.

Module Path

Platform modules are part of the currently used environment and hence are easily available. To let the compiler or VM know about application modules, we have to specify a module path with -mp (as we have done above).

The platform modules in the current environment and the application modules from the module path together comprise the universe of observable modules. Given that set and an initial module contained therein the virtual machine can create a module graph.

Module Graph

Starting from the initial application module, the module system resolves all of its transitive dependencies . The result is the module graph where modules are nodes and a dependency of one module on another is a directed edge.

For our example, that looks like this:

The platform modules are shown in blue, where the brighter modules are the ones we depend on directly and the darker ones transitive dependencies. The ubiquitous java.base is not shown; remember, all modules implicitly depend on it.

Example: Splitting Modules

With this understanding of how modules are handled by the compiler and virtual machine, lets start to think about how to divide our application into modules.

Our architecture consists of the following parts:

  • contacting the microservices and creating statistics
  • updating the database
  • presenting the JavaFX user interface
  • wiring the pieces

Let’s go ahead and create a module for each:

  • com.infoq.monitor.stats
  • com.infoq.monitor.db
  • com.infoq.monitor.ui
  • com.infoq.monitor

The Jigsaw Quick-Start Guide and the JDK itself propose to have a folder for each module below the project’s root source folder. In our case this would have the following structure:

Service Monitor
	 └─ src
	     ├─ com.infoq.monitor
	     │   ├─ com ...
	     │   └module-info.java
	     ├─ com.infoq.monitor.db
	     │   ├─ com ...
	     │   └module-info.java
	     ├─ com.infoq.monitor.stats
	     │   ├─ com ...
	     │   └module-info.java
	     └─ com.infoq.monitor.ui
	         ├─ com ...
	         └module-info.java

This tree is shown truncated here, but each “com” directory represents the package of the same name and would contain more subdirectories and eventually the module’s code.

The statistics module depends on java.base (but as we learned, we don’t have to list that) and uses Java’s built-in logging facilities. It’s public API allows to request aggregated and detailed statistics and is contained in a single package.

module com.infoq.monitor.stats {
    requires java.logging;
    exports com.infoq.monitor.stats.get;
}

To reiterate the accessibility rules: non-public types in com.infoq.monitor.stats.get and all types in other packages are completely hidden from all other modules. Even the exported package is only visible to modules that read this one.

Our database module also logs and obviously requires Java’s SQL features. Its API consists of a simple writer.

module com.infoq.monitor.db {
    requires java.logging;
    requires java.sql;
    exports com.infoq.monitor.db.write;
}

The user interface obviously requires the platform modules that contain the employed JavaFX features. Its API consists of the means to launch the JavaFX UI. This will return a model that uses JavaFX properties. The client can update the model, and the UI will show the new state.

module com.infoq.monitor.ui {
    requires javafx.base;
    requires javafx.controls;
    requires javafx.graphics;
    exports com.infoq.monitor.ui.launch;
    exports com.infoq.monitor.ui.show;
}

Now that we covered the actual functionality, we can turn our attention to the main module, which wires all of the parts together. This module requires each of our three modules, plus Java’s logging facilities. Since the UI requires its dependent module to work with JavaFX properties, it depends on javafx.base as well. And since the main module is not used by anybody else, it has no API to export.

module com.infoq.monitor {
    requires com.infoq.monitor.stats;
    requires com.infoq.monitor.db;
    requires com.infoq.monitor.ui;
    requires javafx.base; // to update the UI model
    requires java.logging;
    // no packages to export
}

It’s a little unwieldy that we have to explicitly require javafx.base, but if we don’t, we couldn’t call any code from javafx.beans.property, which we must do to work with the model. So in a way com.infoq.monitor.ui is broken without javafx.beans.property. For this case the Jigsaw prototype provides the concept of implied readability, which we will cover shortly.

Our modularization creates a very different module graph:

Now let's take a look at some of the commands used to compile, package, and launch our freshly modularized application. This is how we can compile and package modules that have no dependencies outside of the JDK itself:

javac -d classes/com.infoq.monitor.stats ${source files}
jar -c \
    --file=mods/com.infoq.monitor.stats.jar \
    ${compiled class files}

Like before and exactly as with Java 8, we compile the module’s source, write the resulting files into a subfolder of classes with the module’s name, and create a JAR in mods. It’s a modular JAR because the class files include the compiled module descriptor module-info.class.

More interesting is how we process our only module with dependencies on other application modules:

javac \
      -mp mods \
      -d classes/com.infoq.monitor \
      ${list of source files}

jar -c \
      --file=mods/com.infoq.monitor.jar \
      --main-class=com.infoq.monitor.Monitor \
      ${compiled class files}

java -mp mods -m com.infoq.monitor

The compiler needs the application modules we created earlier, and we point it there by specifying the module path with -mp mods. Packaging and launching is as before but now mods contains not just a single module but four of them.

The JVM will start by looking for module com.infoq.monitor because we specified that as our initial module. When it finds that, it will try to transitively resolve all of its dependencies inside the universe of observable modules (in this case, our four application modules and all platform modules).

If it can build a valid module graph, it will finally look up the main method specified with --main-class=com.infoq.monitor.Monitor and launch our application. Otherwise it will fail with an exception informing us of the violated condition, e.g. a missing module or a cyclic dependency.

Implied Readability

A module’s dependency on another module can take two forms.

First, there are dependencies that are consumed internally without the outside world having any knowledge of them. Take for example Guava, where the code depending on a module does not care at all whether it internally uses immutable lists or not.

This is the most common case and covered by readability as described above, where a module can only access another module’s API if it declares its dependency on it. So if a module depends on Guava, other modules are left in blissful ignorance of that fact and would not have access to Guava without declaring their own explicit dependencies on it.

But there is another use case where the dependency is not entirely encapsulated, but lives on the boundary between modules. In that scenario one module depends on another, and exposes types from the depended-upon module in its own public API. In the example of Guava a module’s exposed methods might expect or return immutable lists.

So code that wants to call the dependent module might have to use types from the depended-upon module. But it can’t do that if it does not also read the second module. Hence for the dependent module to be usable, client modules would all have to explicitly depend on that second module as well. Identifying and manually resolving such hidden dependencies would be a tedious and error-prone task.

This is where implied readability comes in:

[We] extend module declarations so that one module can grant readability to additional modules, upon which it depends, to any module that depends upon it. Such implied readability is expressed by including the public modifier in a requires clause.

In the example of a module’s public API using immutable lists, the module would grant readability to Guava to all other modules depending on it by requiring it publicly. In this way its API is immediately usable.

Example: Implying Readability

Let’s turn to our UI module, which exposes a model that uses types from the module javafx.base in its API. We can now change the module descriptor so that it publicly requires that module:

module com.infoq.monitor.ui {
    // expose javafx.base to modules depending on this one
    requires public javafx.base;
    requires javafx.controls;
    requires javafx.graphics;
    exports com.infoq.monitor.ui.launch;
    exports com.infoq.monitor.ui.show;
}

Our main module can now implicitly read javafx.base and no longer has to explicitly depend on it, since its dependency com.infoq.monitor.ui exports it:

module com.infoq.monitor {
    requires com.infoq.monitor.stats;
    requires com.infoq.monitor.db;
    requires com.infoq.monitor.ui;
    // we don’t need javafx.base anymore to update the UI model
    requires java.logging;
    // no packages to export
}

While this changes why com.infoq.monitor reads com.infoq.monitor.stats it does not change the fact that it does read it. Consequently this changes neither our module graph nor the commands required to compile and launch the application.

Beyond Module Boundaries

To quote once again the design overview:

In general, if one module exports a package containing a type whose signature refers to a package in a second module then the declaration of the first module should include a requires public dependence upon the second. This will ensure that other modules that depend upon the first module will automatically be able to read the second module and, hence, access all the types in that module’s exported packages.

But how far should we take this? Look, for example, at the java.sql module. It exposes the interface Driver, which contains a public method getParentLogger() returning aLogger. Because of that the module publicly requires java.logging. So any module using Java’s SQL features can also implicitly access the logging API.

With that in mind, let’s have a second look at our database module:

module com.infoq.monitor.db {
    requires java.logging;
    requires java.sql;
    exports com.infoq.monitor.db.write;
}

Technically the declaration of requiring java.logging is not needed and might seem redundant. So should we drop it?

To answer this question we have to look at how exactly com.infoq.monitor.db uses java.logging. Our module might only need to read it so we are able to call Driver.getParentLogger(), do something with the logger (e.g. log a message) and be done with it. In this case our code’s interaction with java.logging happens in the immediate vicinity of its interaction with Driver from java.sql. Above we called this the boundary between com.infoq.monitor.db and java.sql.

Alternatively we might be using logging throughout com.infoq.monitor.db. Then, types from java.logging appear in many places independent of Driver and can no longer be considered to be limited to the boundary of com.infoq.monitor.db and java.sql.

With Jigsaw being cutting edge, the community still has time to discuss such topics and agree on recommended practices. My take is that if a module is used on more than just the boundary to another module it should be explicitly required. This approach clarifies the system’s structure and also future-proofs the module declaration for refactorings. So as long as our database module uses logging independently of the SQL module I would keep it.

Aggregator Modules

Implied readability opens up the path to so-called aggregator modules, which contain no code on their own but aggregate a number of other APIs for easier consumption. This is already being employed by the Jigsaw JDK, which models compact profiles as modules that simply expose the very modules whose packages are part of the profile.

In case of our microservice monitor we could envision a single API module aggregating the modules for statistics, user interface and data, so that the main module only has a single dependency:

module com.infoq.monitor.api {
    requires public com.infoq.monitor.stats;
    requires public com.infoq.monitor.db;
    requires public com.infoq.monitor.ui;
    // implied readability is not transitive
    // so we have to explicitly list `javafx.base`
    requires public javafx.base
}

module com.infoq.monitor {
    requires com.infoq.monitor.api;
    requires java.logging;
    // no packages to export
}

This is useful in principal, but offers no particular advantage in this simple example.

Services

So far we have covered dependencies that are determined and declared at compile time. A looser coupling can be achieved if a dependency takes the form of a service, where one or more modules provide a functionality, abstracted behind a single type, and others consume instances of that type. The module system can be used by the consumer to discover providers. This implements the service locator pattern, where the module system itself acts as the locator.

A service is a set of interfaces and (usually abstract) classes that provide a cohesive feature. All involved types must be accessible from a single type (likely an interface) so that one can be used to load the service.

A service provider module contains one or more implementations of a service. For each it includes a provides X with Y; clause in its module descriptor, where X is the fully qualified name of the service interface and Y the fully qualified name of the implementing class. Y needs to have a public, parameterless constructor so that the module system can instantiate it.

The service consumer module has to read the service module and include a uses X; clause in its descriptor. At runtime it can then call ServiceLoader.load(Class<X>) and get a loader for the service interface. The loader is iterable and contains instances of all classes that implement X and were provided by service provider modules.

When services are used as described here, the coupling is not only loosened at compile time (because the consumer does not declare dependencies on the providers). It is also loose at runtime because the module system will create no read edge from consumer to provider.

Another interesting aspect is that the set of available providers for a service is defined by the module path (i.e. usually at launch time). Exactly those observable modules that provide implementations of a service will be available at runtime via the service loader. So a system’s behavior can be influenced by editing the content of its module path and restarting it.

Example: Creating And Using Services

In our example we have a module com.infoq.monitor.stats, which we said contacts the services running in our network and creates statistics. This sounds like a lot of work for a single module, but we can split it up.

Watching individual services is a uniform task so we create an API for that and put it into the new module com.infoq.monitor.watch. The service interface is called com.infoq.monitor.watch.Watcher.

We are now free to create one or more modules with implementations for concrete microservices. Let's call these modules com.infoq.monitor.watch.login, com.infoq.monitor.watch.shipping and so forth. Their module descriptors are as follows:

module com.infoq.monitor.watch.login {
    // we need the module defining the service we are providing;
    // we imply readability so this module is usable on its own
    requires public com.infoq.monitor.watch;
    provides com.infoq.monitor.watch.Watcher
        with com.infoq.monitor.watch.login.LoginWatcher;
}

Note that they only provide implementations of Watcher but export no packages.

With all the code that contacts microservices carved out of com.infoq.monitor.stats, we now have to make sure that it still has access to that functionality. Its new module-info.java:

module com.infoq.monitor.stats {
    requires java.logging;
    requires com.infoq.monitor.watch;
    // we have to declare which service we are depending on
    uses com.infoq.monitor.watch.Watcher;
    exports com.infoq.monitor.stats.get;
}

Somewhere in its code we can now do this:

List<Watcher> watchers = new ArrayList<>();

ServiceLoader.load(Watcher.class).forEach(watchers::add);

That produces a list containing one instance of each Watcher implementation that was provided by modules on the module path. From here on out the code would be similar to before, i.e. contacting the services and creating statistics.

Limiting our module graph to com.infoq.monitor.stats and below (because everything else is unchanged) our new version looks as follows:

Note how the arrows all point towards the new module; a typical example for the dependency inversion principle.

Compilation, packaging and launching is like before (except that now we have more modules).

Migration

So far we have discussed the scenario where a complete application including all its dependencies was turned into modules. But when Jigsaw is first removed from its shiny new package, that will not be very common; most projects will have dependencies on libraries that were not yet adapted to work with the module system, and over which they will have no control.

The Jigsaw team has tackled this problem head-on and provides a gradual migration path to modularization. For this they introduced two kinds of modules that we have not yet discussed.

Kinds Of Modules II

We already know about platform modules and application modules. They are fully aware of the module system, and their defining characteristic are their module descriptors. Because these give them a name, they are called named modules.

There are two other types of modules for artifacts that are not aware of the module system.

Before Jigsaw, all types loaded from the classpath ended up in the same space where they could freely access each other. This unstructured space continues to exist: Each class loader has a unique unnamed module, to which it assigns all types it loaded from the classpath.

Unnamed modules read all other modules and export all packages. Because a modularized application should not depend on the random content of the classpath, named modules can not require unnamed modules and are therefore unable to read them (without resorting to reflection).

But an artifact without a module descriptor can also be placed on the module path. In this case, the module system will create a fully fledged module for it, called an automatic module.

An automatic module’s name is derived from the artifact’s file name, it can read all other modules and exports all its packages. Because the module system can easily check at launch time whether any specific automatic module is present on the module path, named modules can depend on them and hence read them.

So in the common case of a single application class loader, the application’s universe of observable modules can consist of:

  • named platform modules as they are contained in the run time
  • one named application module for each artifact on the module path that has a module descriptor
  • one automatic module for each artifact on the module path that does not have a module descriptor
  • a unique unnamed module composed of all artifacts on the classpath, regardless of whether they have module descriptors or not (with several application class loaders there would be several unnamed modules)

Migration Strategies

These kinds of modules open up the path to a gradual migration to the module system. (Note that module relationships are not the only hurdle, though.)

As has been stated above, the whole application, including all of its dependencies, can simply be put on the classpath. This is a crucial escape hatch in case some problem prevents a migration.

We can now see why that approach works: All artifacts on the classpath get rolled into the unnamed module, where all types can freely access each other. To use Java’s public API they have to access the platform modules, which they can do since the unnamed module reads all other observable modules.

A bottom-up migration starts with dependency-free artifacts, which can immediately be modularized. Building on that, other projects can move to Jigsaw.

Clients can put modular JARs on the module path and reference them by name if their projects were migrated. Even if not and their code still comes from the classpath, it can access the migrated artifacts because the unnamed module can read all other modules. Or clients can decide to put modular JARs on the classpath.

This approach works best for library projects with few, well-maintained dependencies. But as the number of dependencies grows, projects might not be willing to wait for all of them to be modularized. This is especially true of large Java applications, which might prefer to opt for another approach.

A top-down migration starts with creating module descriptors for all artifacts of a project. They need names and must specify which other internal artifacts they depend on and which packages they want to export.

This process will naturally be confronted with external dependencies. If Jigsaw-aware versions exist for them and can be used, that’s great. If not, automatic modules are the way to go: The project’s artifacts require modules with the name that Jigsaw derives from the artifact file names, and the artifacts are placed on the module path.

It is sufficient to do this for direct dependencies so that the new application modules can access them. The dependencies likely bring transitive dependencies with them. But because the direct dependencies were turned into automatic modules, which read all other modules including the unnamed one, their dependencies can be put on the classpath.

For large projects this manual approach becomes unusable and build tools must help. Gradle and Maven have already started to work on Jigsaw-related features.

More details about migration can be found in the JavaOne talk Advanced Modular Development by Alex Buckley and Alan Bateman, both members of the Jigsaw team at Oracle.

Example: Migrating Dependencies

Let’s say our database module uses Guava and we have the artifact guava-19.0.jar in a directory libs. We can not simply put it on the classpath because our application is already properly modularized and we discussed above that named modules can not read the unnamed module. So we need an automatic module.

From a file named guava-19.0.jar Java derives the module name guava. With this knowledge we can update the database module’s descriptor:

module com.infoq.monitor.db {
    requires java.logging;
    requires java.sql;
    requires guava;
    exports com.infoq.monitor.db.write;
}

To compile it, we need to add libs to the compiler’s module path:

javac \
    -mp libs \
    -d classes/com.infoq.monitor.db \
${list of source files}

Packaging requires no changes. If we launch our application as we did before, the JVM will complain that it couldn’t find the module guava. To fix that we need to add libs to the module path:

java -mp mods:libs -m com.infoq.monitor

(Note that on Windows the separator between paths is “;” not “:”.)

Next Steps

We have explored the Jigsaw prototype and seen the core features it has to offer. What else can we do besides waiting for Java 9?

Go deeper

It is always possible to learn more and there are a couple of advanced topics we did not discuss:

The excellent “State of the Module System” shows how modules can be used with reflection, which includes adding read edges at run time and the new concept of layers, as well as the interaction with class loaders.

The new tool jlink can be used to create run time images that only contain a specific set of platform modules; it is introduced in the Jigsaw Quick-Start Guide, which is highly recommended.

These topics are also covered by talks the Jigsaw team gave at JavaOne 2015 and Devoxx BE 2015. I summarized the former here.

Observe

All things Jigsaw should be discoverable from the project’s OpenJDK site. The main source for up-to-date information on Project Jigsaw is the Jigsaw-Dev mailing list. I will also continue to discuss this topic on my blog.

Prepare

As has been hinted at, a migration to Jigsaw can be a little rough. To prepare our projects we should examine whether they rely on anything that will be unavailable or removed in Java 9.

Dependencies on internal APIs, a crucial obstacle, can be analyzed with jdeps, the Java Dependency Analysis Tool (introduction with some internal packages, official documentation for Windows and Unix), which is already available in JDK 8. There are also at least three jdeps-plugins for Maven: by Apache, Philippe Marschall and myself. The latter enables a project to gradually remove its dependencies on internal APIs while breaking the build on relapses.

If you are concerned about some specific API that will be unavailable in Java 9, you could check the mailing list of the corresponding OpenJDK project as these will be responsible for developing public versions of them.

We should also identify the critical dependencies our projects rely on and check with those teams how they are preparing for Java 9.

Adopt

Jigsaw early access builds are available and can be used to tentatively compile and run existing projects. Unfortunately build system support is still incomplete, but it’s being worked on.

Information and problems gathered this way can be returned to the project by posting on the Jigsaw-Dev mailing list. To quote the (almost) final words from one of the many involved JEPs:

It is impossible to determine the full impact of these changes in the abstract. We must therefore rely upon extensive internal and—especially—external testing. […] If some of these changes prove to be insurmountable hurdles for developers, deployers, or end users then we will investigate ways to mitigate their impact.

There is also the global Java User Group AdoptOpenJDK, which is a great contact for early adopters.

About the Author

Nicolai Parlog is a software developer and Java enthusiast. He constantly reads, thinks and writes about Java, and codes for a living as well as for fun. He's a long tail contributor to several open source projects and blogs about software development on CodeFX. You can follow Nicolai on Twitter.

 

Rate this Article

Adoption
Style

BT