Introduction to Java Modules: A Comprehensive Tutorial

Java 9 introduces a new feature called Java Modules, which is part of the Java Platform Module System (JPMS). The Java Platform Module System, also known as Project Jigsaw, brings several benefits to Java developers, including smaller application distributables, encapsulation of internal packages, and startup detection of missing modules. In this tutorial, we will explore the basics of Java modules, their structure, and how to use them effectively.

Table of Contents

  1. What are Java Modules?
  2. Benefits of Java Modules
  3. Smaller Application Distributables
  4. Encapsulation of Internal Packages
  5. Startup Detection of Missing Modules
  6. Java Module Basics
  7. Module Naming
  8. Creating a Java Module
  9. Module Declarations
  10. Module Dependencies
  11. Using Java Modules in an Application
  12. Migrating from Java 8 to Java 17
  13. Conclusion

A picture showing a scren with java source code

1. What are Java Modules?

Java Modules provide a packaging mechanism that allows you to package a Java application or Java API as a separate module. A Java module is packaged as a modular JAR file. It consists of one or more Java packages that are closely related and belong together. Each module has a unique name, following the same naming rules as Java packages.

A module contains packages and resources, such as media or configuration files, that are specific to that module. By organizing code and resources within modules, developers can achieve better modularity and encapsulation, making their projects more manageable.

2. Benefits of Java Modules

Java Modules bring several benefits to Java developers. Let’s explore the major advantages of using Java Modules in your projects.

2.1 Smaller Application Distributables

One of the significant benefits of Java Modules is the ability to create smaller application distributables. In traditional Java applications, all the Java Platform APIs were bundled together, resulting in larger distributables. With Java Modules, you can specify the exact modules of the Java Platform APIs that your application requires. This allows Java to package your application with only the necessary modules, reducing the size of the distributable.

Before Java 9, developers had to include all the Java Platform APIs with their Java applications because there was no reliable way to determine the classes used by the application. This led to unnecessary inclusion of classes that the application did not use, making the distributables larger than necessary.

By leveraging Java Modules, you can optimize the size of your Java application distributables, which is crucial for resource-constrained environments such as mobile devices or embedded systems.

2.2 Encapsulation of Internal Packages

Java Modules enforce encapsulation by allowing modules to explicitly specify which packages are visible to other modules. A module can define which packages it exports, making them accessible to other modules, while keeping other packages hidden or encapsulated.

This encapsulation of internal packages provides a clear separation between the public API and the internal implementation details of a module. It helps prevent unwanted dependencies and strengthens the modular structure of the application. Modules can have hidden packages that are not accessible to other modules, ensuring that they are only used internally within the module.

Encapsulation enhances code maintainability, as changes to internal packages do not impact other modules or cause unexpected side effects. It also promotes better design practices by encouraging developers to define clear boundaries between the public and internal parts of their modules.

2.3 Startup Detection of Missing Modules

With the introduction of Java Modules, the Java Virtual Machine (JVM) performs startup detection of missing modules. When a Java application is packaged as a module, it specifies the other modules it requires. During JVM startup, the dependency graph of modules is checked, and if any required modules are missing, the JVM reports the issue and shuts down.

In previous Java versions, missing classes or modules were only detected at runtime when the application tried to use them. This could result in unexpected errors or failures during execution, making it harder to identify and resolve the issues.

Startup detection of missing modules provides early feedback on module dependencies, allowing developers to address missing modules and resolve any issues before the application is fully executed. This improves the reliability and stability of Java applications, reducing the chances of runtime errors due to missing dependencies.

3. Java Module Basics

To understand Java Modules better, let’s dive into the basics of how modules are structured and how they work.

3.1 Modules Contain One or More Packages

A Java module is a collection of one or more Java packages that are logically related and belong together. It encapsulates related code and resources within a single unit, providing better organization and modularity.

The packages inside a module are similar to the packages used in traditional Java projects. They help organize the code and provide a namespace for classes and interfaces. However, in Java Modules, packages also play a role in determining the visibility of code outside the module.

3.2 Module Naming

Each Java module must be given a unique name. The module name follows the same naming rules as Java packages. However, it’s recommended to avoid using underscores (_) in module names, as underscore may be reserved for future use by the Java language.

A valid module name could be com.example.mymodule, reflecting a naming convention similar to Java packages. The module name should be descriptive and meaningful, representing the purpose or functionality of the module.

Choosing appropriate module names helps developers understand the purpose of the modules and facilitates module identification and management in larger projects.

4. Creating a Java Module

Creating a Java module involves several steps, from defining the module structure to declaring dependencies. Let’s go through the process of creating a Java module step by step.

4.1 Module Declarations

To create a Java module, you need to define a module descriptor file, also known as module-info.java. The module descriptor file acts as the entry point for the module and contains important metadata about the module.

The module descriptor file declares the module’s name, the packages it exports (visible to other modules), the modules it requires, and other optional configurations. This file is placed in the root directory of the module.

Here’s an example of a module descriptor file:

module com.example.mymodule {
    exports com.example.mypackage;
    requires java.base;
}

In the above example, the module com.example.mymodule is declared. It exports the com.example.mypackage package, making it visible to other modules. Additionally, it requires the java.base module, which is one of the core modules provided by the Java Platform.

The module descriptor file provides a clear definition of the module’s dependencies and visibility, ensuring proper encapsulation and modularization.

4.2 Module Dependencies

Java Modules allow you to declare dependencies between modules. By declaring module dependencies, you ensure that the required modules are available at runtime.

To declare a module dependency, you specify the required module in the module descriptor file using the requires keyword. The requires directive tells the JVM that the current module depends on the specified module.

Here’s an example of declaring a module dependency:

module com.example.mymodule {
    requires com.example.dependencymodule;
}

In the above example, the module com.example.mymodule requires the module com.example.dependencymodule. This ensures that the required module is present when running the application.

By managing module dependencies, you can control the dependencies between modules, enforce encapsulation, and ensure the correct runtime environment for your Java application.

5. Using Java Modules in an Application

Once you have created Java modules, you can use them in your applications by specifying the required modules during compilation and runtime. Let’s explore how to use Java modules in an application.

5.1 Compiling Java Modules

When compiling Java modules, you need to specify the module path using the -p or --module-path option. The module path specifies the location of the module JAR files or directories containing the compiled modules.

Here’s an example of compiling Java modules:

javac -p modulepath -d outputdir module-src/module-info.java module-src/com/example/mypackage/MyClass.java

In the above example, we compile the module com.example.mymodule located in the module-src directory. The module path is specified using the -p option, and the output directory is specified using the -d option.

5.2 Running Java Modules

To run Java modules, you need to specify the module path and the main module using the -p or --module-path option and the -m or --module option, respectively.

Here’s an example of running Java modules:

java -p modulepath -m com.example.mymodule/com.example.mypackage.MyClass

In the above example, we run the main class com.example.mypackage.MyClass from the module com.example.mymodule. The module path is specified using the -p option, and the main module is specified using the -m option.

By correctly specifying the module path and the main module, you can run Java applications that utilize Java modules.

6. Migrating from Java 8 to Java 17

If you’re transitioning from Java 8 to Java 17, it’s important to understand the changes introduced in Java 9 regarding Java Modules. Migrating to Java 9 and beyond requires updating your code to work with modules and making necessary changes to module declarations and dependencies.

To learn more about migrating from Java 8 to Java 17, refer to the article Java 8 to Java 17: New Features and Hassle-Free Migration. The article provides a comprehensive overview of the new features introduced in each Java version and guides you through the migration process.

7. Conclusion

Java Modules bring significant improvements to the Java ecosystem, enabling better modularity, encapsulation, and dependency management. In this tutorial, we explored the basics of Java Modules, their benefits, and how to create and use modules in your Java applications. Understanding Java Modules and incorporating them into your projects will help you write more maintainable, scalable, and efficient Java code.

Continue your journey with Java 9 and beyond, leveraging the power of modules to enhance your application development experience. Embrace the modular nature of Java Modules and enjoy the benefits of a more organized, scalable, and reliable codebase.

Passionate Archer, Runner, Linux lover and JAVA Geek! That's about everything! Alexius Dionysius Diakogiannis is a Senior Java Solutions Architect and Squad Lead at the European Investment Bank. He has over 20 years of experience in Java/JEE development, with a strong focus on enterprise architecture, security and performance optimization. He is proficient in a wide range of technologies, including Spring, Hibernate and JakartaEE. Alexius is a certified Scrum Master and is passionate about agile development. He is also an experienced trainer and speaker, and has given presentations at a number of conferences and meetups. In his current role, Alexius is responsible for leading a team of developers in the development of mission-critical applications. He is also responsible for designing and implementing the architecture for these applications, focusing on performance optimization and security.