Java Modularity Part 2 – Keywords and Descriptors

Java modularity

We introduced the concepts of modularity in the last post. This week we’ll look at some of the more technical details around modularity, including the new keywords relating to modules, and how to create a module.

Quick Revision of Java Modularity

Modules are a new way of grouping and organising our Java code better. A module is simply a set of related packages with explicitly stated dependencies on other modules.

We can decompose a large application into a number of modules. Each module can be built as a separate smaller JAR file and deployed as needed, rather than deploying a large JAR file for the entire application. A module can be deployed by itself.

Module Declarations and Descriptors

Modules need to explicitly declare which modules they depend on, and which packages they export. These dependency declarations improve the integrity, security and maintainability of the code.

Modules describe these dependencies in a module declaration. This is a Java source code file called module-info.java. Hyphens are not allowed in Java class names, but the module descriptor file uses the hyphen. The naming is similar to the package-info.java file that contains package annotations and javadoc comments to document a package.

After the module declaration has been compiled with a standard Java compiler, it becomes the module descriptor. A module must include a module descriptor. It is deployed in the root directory of the module’s file hierarchy.

The module descriptor contains metadata that specifies the module’s dependencies, the packages the module exposes to other modules, the packages it requires, etc. The JDK uses the module descriptors to verify the dependencies and interactions between modules both at compile-time and at runtime.

The module declaration contains the following:

  • The name of the module. Module names are normally written in lowercase (similar to packages).
  • exports – Specifies the packages within the module that will be available to other modules. All other packages in the module are implicitly hidden.
  • requires – Specifies the modules on which this module depends.
  • uses – Specifies the services that the current module uses (consumes).
  • provides – Specifies the services that the current module provides.
  • open – Specifies the classes in the module that can be accessed using the Reflection API.

Each module declaration starts with the keyword module which is followed by a unique module name and a module body enclosed in braces. The exported packages and required modules will be specified inside the braces. This is very similar to the way we create a Java class:

module modulename { 
    // module metadata goes here
}

Module Directives

The body of the module declaration can be empty or it may contain various module directives. These directives include exportsmoduleopenopensprovidesrequiresuseswithto and transitive. These are contextual keywords. They are keywords only in the context of module declarations, and may be used as normal identifiers in other Java code.

Module Naming Rules

The module system relies on the uniqueness of a module’s name. We will have problems if we have conflicting module names, or names that change between application versions. It is very important to have stable and globally unique module names.

This means that when we design our system, we must put a lot of effort into good package design. We must decide which classes are placed in which package. We must decide on unique package names. The packages and their contents must be stable and conform to good object oriented design. That’s a blog post all on its own!

The best way for us to ensure that the module names are globally unique is to use the common reverse-domain naming scheme that we already use for packages:

module com.incusdata.office {
    // module metadata goes here
}

Note: The fullstops are part of the module name. They are not interpreted as subdirectory path separators, as is done with packages.

The exports and requires Keywords

The two main keywords in a module declaration are requires and exports.

By default, a module doesn’t expose its API to other modules. This gives strong modular encapsulation. This makes our code more secure, but in order for anyone to be able to use it, we need to explicitly expose our API. We use the exports directive to expose all public members of the named package.

There are two types of packages in a module: exported packages and concealed packages.

  • Exported packages are intended to be used outside of the module. Any class in any other module can use these packages.
  • Concealed packages are not intended to be used outside the module. They are internal to the module and can be used inside the module only. If a package is not explicitly exported, then it is implicitly hidden.

We also need to specify if this module depends on any other modules. We use the requires directive to specify module dependencies.

Java Modularity: Example

For example, let’s say we are building an application that will track employees in an office environment. It would probably have a data access layer containing a DAO class, a service layer which would contain an EmployeeProcessor to process Employee entity objects. These classes would be contained in similarly named packages. The DAO package might be a more general package not being part of the office application.

The following module declaration declares that the module com.incusdata.office depends on the com.incusdata.dao module, and exports the two packages com.incusdata.office.entity and com.incusdata.office.service:

module com.incusdata.office {
    requires com.incusdata.dao;
    exports com.incusdata.office.entity;
    exports com.incusdata.office.service;
}

The requires directive specifies that the module com.incusdata.office has both a runtime and a compile-time dependency on com.incusdata.dao.

The exports directive specifies that the public members of com.incusdata.office.entity and com.incusdata.office.service packages will be accessible by any other dependent modules. Only the listed package itself is exported. No sub-packages of the exported package are exported. Private members are not accessible even if we try to use reflection. However, we can specifically permit certain forms of access depending on the command line options we use at runtime.

What’s Next?

In the next post, we’ll look at a more comprehensive example. We’ll also look at the other syntax used with the requires and exports keywords.

I’m always interested in your opinion, so please leave a comment. Your feedback helps me write tips that help you.

Leave a Comment

Your email address will not be published. Required fields are marked *

Code like a Java Guru!

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.

Your Java tip is on its way!

Check that incusdata.com is an approved sender, so that your Java tips don’t land up in the spam folder.

Our privacy policy means your data is safe. You can unsubscribe from these tips at any time.