Introduction to Java Module System in Java 7.0

This article explains the new Java Module System that will be included in the Java 7.0 release. Modules are new to the java language and they provide a standard for developing and deploying applications. The article will explain the various sub components that are available as part of the Java Module System’s architecture. The various sections discussed in the article will provide in-depth details about the module definition, the metadata associated with the module, the versioning system and the repositories for storing and retrieving the module definitions.

Java Module System

also read:

The architecture of Java Module System consists of three main components:

  • Java Module
  • Versioning System
  • Repository

A Java Module is a distribution standard that contains set of classes and resources similar to a Java Archive File. What differs from JAR from a JMS (Java Module System, not Java Messaging System) is that the modules can be versioned. The Java Module System contains a metadata file that contains information about the inclusion of classes, resources and the set of jar files that this module is dependant on. Versioning of a java module is explained in detail in the following section. The specification of Java Module System also defines a repository whose Java Module files can be stored, discovered and used by other modules.

Module

Before going into more details, let us look into the various terminologies and the individual components that are related to a Java Module System.
A module or a module definition can be defined as a logical unit of set of files, resources and its dependencies that can be versioned, packaged and deployed in the module repository for re-use by some other application. Each module consists of a module meta-data that is self-describing. Given below is the major breakup of a module metadata,

  • Name of the module
  • Extensible meta-data that includes version, resources, exports from this module, etc..
  • List of imported modules
  • List of classes contained in this module

For example, consider the following metadata for a module definition by name net.javabeat.config with the version 1.0,

module
(
    name = net.javabeat.config
    extensible-metadata = [@Version("1.0")]
)

Module Exports

Classes and resources that are available in one module can be exported so that other modules can re-use by importing them. For example, consider that a module called net.javabeat.util is developed containing three classes: ClassA, ClassB and ClassC. Assume that ClassC is a public utility class that is to be re-used by some other modules. In this case, the requirement can be made to achieve by having the following module definition,

module
(
    name = net.javabeat.util
    extensible-metadata = [@Version("1.0")]
    class-exports =
                [net.javabeat.util.ClassC]
    members =
                [net.javabeat.util.ClassA,
                 net.javabeat.util.ClassB,
                 net.javabeat.util.ClassC]
)

Module Imports

A module can import other module for accessing the classes and the resources. Note that, only the set of classes and the resources that are exported can be referenced and used by the imported module. The following module net.javabeat.app imports the example module net.javabeat.util that was created in the preceding example,

module
(
    name = net.javabeat.app
    extensible-metadata = [@Version("1.0")]
    imports =
            [ImportModule(net.javabeat.util, @VersionConstraint("1.0"))]
    members =
            [net.javabeat.app.ClassA,
            net.javabeat.app.ClassB,
            net.javabeat.app.ClassC]
)

Versioning the Java Module System

Versioning a Java Module follows the versioning scheme standard as defined by the specification. More specifically, the version for a Java Module contains the following components,

  • Major version
  • Minor version
  • Micro version
  • Update version
  • Qualifier

The syntax to define a version is Major.Minor.Micro.Update.Qualifier. All the above components have the data type as a number, except for Qualifier which takes a string value. The only mandatory component in the versioning scheme is the major version and the rest of the components, if omitted, will take the sensible defaults. For example, consider the following version,

5.1-beta-testing

The major and the minor components are 5 and 1 respectively, whereas qualifier takes the value as beta-testing. The rest of the components micro and update are assumed to take the value 0.

Significance

Let us try to understand the significance of the different components in the versioning scheme and their usage scenarios. Consider the development going for an imaginary product called MyProduct for a milestone release m1. For the sake of simplicity, let us assume that the product has a single interface and a single concrete implementation class for the interface. Let us assume that the name of the interface is Product.java containing one single method run() and the concrete implementation class is ProductImpl.java.
Once the development is done, the class/interface will be packaged, versioned and deployed in the java module repository so that other clients can use it. In this case, let us assume that the name given for the module is my-product and the version for the module is 1-m1. So it is understood that the values for the major component and the qualifier are 1 and m1 and the components minor, micro and update hold the value of 0.
Assume that a low priority bug is raised against the product and the fix mandates some minor changes within the implementation of ProductImpl. In this case, the significance of the update version comes into picture because any minor changes in the product mandates the update version to get incremented. It should be noted that the software remains backward compatible at this stage. Now the module name along with the module version looks like,

my-product-1.0.0.1-m1.

Let us assume that the implementation of the run() method defined in ProductImpl.java needs to be changed because of some performance related issue. In this case, the micro version number has to be incremented and the update version has to be reset. Note that in this stage also, the module remains backward compatible and it looks like,

my-product-1.0.1.0-m1

Minor version should be incremented for any medium level changes in the product and accordingly, the micro and the update version should be reset to zero. Medium level changes cannot ensure backward compatibility and hence, for a safer use, the client code and other components that is dependant on this module has to be updated. Let us assume that after incorporating medium level changes in the product, the module has become,

my-product-1.1.0.0-m1

There is a demand for change in the signature of the method Product.run() which accepts different number of parameters. Such changes in existing APIs or inclusion of newer client facing APIs are ideal candidates for incrementing the major version number in which case backward compatibilities is not possible. API level changes are considered as major changes in which case, the minor, macro and update version numbers have to be set to zero, that may look like,

my-product-2.0.0.0-m1.

Let us imagine that the first milestone release of the product is completed and the product is successfully delivered and the development has already started for the second milestone, in which case the qualifier has to be updated. After updating, the module may look like,

my-product-2.0.0.0-m2

Module Repositories

A module repository defines a standard way for storing and retrieving the modules, multiple versions of the same module can also be stored and retrieved. There can be multiple module repositories for a single JVM, and it is always possible for an application to create its own module repository for hosting java modules. Following are the different types of modules repository.

  • Bootstrap Repository
  • Global Repository
  • Application Repository
  • URL Repository

The Bootstrap Repository is always available in the JVM and it is responsible for loading the core classes available in the JDK. A Global Repository points to a shared location containing set of modules that are shared and used by multiple JVM’s. Application Repository, typically points to modules that are defined and used by an application and an URL Repository points to modules that are to be downloaded and used from a particular URL on some server machine.
The module repositories, like class loaders, follow the hierarchical delegation approach. For example, an implementation of a custom module repository will try to delegate the finding and the loading of the module to its parent module repository, which is usually the bootstrap repository. If the parent repository can find the module definitions, then the child repository will do nothing other then delegating the control to the next level of processing. Only if the parent module repository cannot find the module definition, the child will try to find and load the module definitions by scanning through the application’s class path.

Operations on Repositories

A Java module Repository is represented by java.lang.module.Repository class. For instance, the following code obtains a reference to the Bootstrap repository,

Repository bootstrapRepository = Repository.getBootstrapRepository();

Now, let us look into the various important operations that can be performed on a module repository.

Life-cycle operations

Initialization and shutdown are the two life-cycle operations that are applicable to any type of repository. A newly created repository object won’t be active unless the initialize() is called on the repository object,
The module repository framework will call the shutdown()method when the modules will no longer be referenced by the application. Note that when this method is called the repository will be inactive.

Looking-up the module definitions

For browsing all the modules that are available in repository, the findAll() method is available. For example, the following code will return a list of module definitions from the given active repository.

 moduleDefinitions = repository.findAll();

Note that the class ModuleDefinition encapsulates all the information that are available in the module’s metadata file. For example, the name and the version of a module definition, as specified in the metadata file can be obtained using the following code,
It is also possible to find a particular module in the repository when module name is provided.
The Java Module System also provides a powerful search mechanism that offers a filtering criteria for selecting set of module definitions within a particular version range. The following example fetches a list of module definitions with the name org.testand whose versions are above 7.0,

 moduleDefinitions = repository.findAllI(moduleName, constaint);

Module Illustration

Note that the API in the Java module system has been changing and the source code hasn’t been officially released to the public yet. The syntax and the usage cases provided in this section might get changed once the stable release is made,
For illustration purposes, assume that there are four classes by name Service, ServiceImpl, ServiceManager and ServiceMain in a package called net.javabeat.service
To indicate to the framework that the classes Service, ServiceImpl, ServiceManager and ServiceMain belong to the module net.javabeat.service, then the following changes have to be made,
Service.java

module net.javabeat.service;
package net.javabeat.service;
class Service { }

ServiceImpl.java

module net.javabeat.service;
package net.javabeat.service;
class ServiceImpl { }

ServiceManager.java

module net.javabeat.service;
package net.javabeat.service;
class ServiceManager { }

ServiceMain.java

module net.javabeat.service;
package net.javabeat.service;
class ServiceMain {
 public static void main(String args[]) { }
}

Every source file has to be moduled with the module name to indicate that the class belongs to the module. Now to create the module itself the following java file has to be created first,
service-module.java

@Version("1.0")
@MainClass("net.javabeat.service.ServiceMain") module net.javabeat.service;

First compile all the source files including the one that contains the definition of the modules using the following command,

javac –d . net/javabeat/service/*.java

The next step is to create the java module file that contains the various class files. Type the following command to create the module file called net.javabeat.service with version 1.0. Note that jam stands for Java Archive Module (JAM).

jam cvf net.javabeat.service-1.0.jam net/javabeat/service/*

To run the java module containing the main class, use the new option –jam available as part of the java.exe as follows,

java –jam net.javabeat.service-1.0.jam

Conclusion

An attempt has been made to provide the architecture details of the java module system that helps to ease the application’s development in terms of packaging, versioning and deployment. The initial section of this article concentrated more of the module definition, its associated metadata as well as the capability of a module to export and import resources. The versioning system associated with the module and the various sub-components are discussed in greater details along with examples. The need for module repositories for storing and retrieving the module definitions, as the different types of repositories and the various core operations that can be performed on a repository are also explained. The article is concluded by providing an illustration of creating, deploying and running a module.

Comments

comments

About Krishna Srinivasan

He is Founder and Chief Editor of JavaBeat. He has more than 8+ years of experience on developing Web applications. He writes about Spring, DOJO, JSF, Hibernate and many other emerging technologies in this blog.

Speak Your Mind

*