Introduction to Java Modules and Project Jigsaw

Java has been a cornerstone in the world of programming for decades. However, as the language evolved and large - scale projects became more complex, there was a need for better modularity. Project Jigsaw, introduced in Java 9, aimed to address these issues by bringing module support to the Java platform. This blog will provide a comprehensive introduction to Java Modules and Project Jigsaw, covering fundamental concepts, usage methods, common practices, and best practices.

Table of Contents

  1. Fundamental Concepts
  2. Usage Methods
  3. Common Practices
  4. Best Practices
  5. Conclusion
  6. References

Fundamental Concepts

What is Project Jigsaw?

Project Jigsaw is an initiative to introduce a standard module system to the Java platform. It was developed to solve several long - standing problems in Java, such as the lack of strong encapsulation, large monolithic JRE, and difficulty in managing dependencies in large projects.

Java Modules

A Java module is a self - contained unit of code that has a well - defined set of classes and resources. It can specify which of its packages are accessible to other modules (exports) and which other modules it depends on (requires).

The module-info.java file is the heart of a Java module. It is a special file that must be placed in the root of the module’s source directory. Here is a simple example of a module-info.java file:

// module-info.java
module com.example.myModule {
    exports com.example.myPackage;
    requires java.base;
}

In this example:

  • module com.example.myModule declares a module named com.example.myModule.
  • exports com.example.myPackage makes the com.example.myPackage package accessible to other modules.
  • requires java.base indicates that this module depends on the java.base module, which is the foundation module of the Java platform.

Usage Methods

Creating a Simple Module

Let’s create a simple Java module. First, create a directory structure for your module:

myModule/
├── src/
│   └── com/
│       └── example/
│           └── myPackage/
│               ├── MyClass.java
│               └── module-info.java

Here is the code for MyClass.java:

// MyClass.java
package com.example.myPackage;

public class MyClass {
    public static void sayHello() {
        System.out.println("Hello from MyClass!");
    }
}

And the module-info.java file:

// module-info.java
module com.example.myModule {
    exports com.example.myPackage;
}

Compiling a Module

To compile the module, you can use the javac command:

javac -d mods/com.example.myModule src/com/example/myPackage/*.java src/module-info.java

Running a Module

You can create a simple application that uses this module. Create another module that depends on com.example.myModule:

myApp/
├── src/
│   └── com/
│       └── example/
│           └── myApp/
│               ├── Main.java
│               └── module-info.java

Here is the code for Main.java:

// Main.java
package com.example.myApp;

import com.example.myPackage.MyClass;

public class Main {
    public static void main(String[] args) {
        MyClass.sayHello();
    }
}

And the module-info.java file:

// module-info.java
module com.example.myApp {
    requires com.example.myModule;
}

Compile the myApp module:

javac -p mods -d mods/com.example.myApp src/com/example/myApp/*.java src/module-info.java

Run the application:

java -p mods -m com.example.myApp/com.example.myApp.Main

Common Practices

Dependency Management

When working with Java modules, it’s important to manage dependencies correctly. Only require the modules that your module actually needs. This helps in reducing the size of the application and improving security.

Encapsulation

Use the exports statement carefully. Only export the packages that need to be accessed by other modules. This helps in hiding the internal implementation details of your module.

Versioning

Although Java modules do not have built - in versioning, you can use external tools like Maven or Gradle to manage module versions. This is important for maintaining compatibility between different versions of modules.

Best Practices

Use Module - Specific Services

Java modules support the concept of services. A service is an interface or an abstract class, and service providers are implementations of that service. By using services, you can achieve loose coupling between modules.

Here is an example of a service interface:

// ServiceInterface.java
package com.example.service;

public interface ServiceInterface {
    void performAction();
}

And a service provider:

// ServiceProvider.java
package com.example.service.impl;

import com.example.service.ServiceInterface;

public class ServiceProvider implements ServiceInterface {
    @Override
    public void performAction() {
        System.out.println("Service action performed!");
    }
}

In the module-info.java of the service provider module, you can declare the service:

// module-info.java
module com.example.serviceModule {
    provides com.example.service.ServiceInterface with com.example.service.impl.ServiceProvider;
    exports com.example.service;
}

Keep Modules Small and Cohesive

Each module should have a single, well - defined responsibility. This makes the modules easier to understand, test, and maintain.

Conclusion

Java Modules and Project Jigsaw have brought a new level of modularity to the Java platform. By understanding the fundamental concepts, usage methods, common practices, and best practices, developers can build more robust, secure, and maintainable Java applications. The module system helps in managing dependencies, improving encapsulation, and enabling better code organization.

References