A system of Java modules for beginners

A system of Java modules for beginners

Hello, Habre!

System of modules in Java 9, known as Project Jigsawwas conceived and implemented to address a number of issues, including “JAR Hell” and difficulties with providing strong encapsulation.

And here it is with Java 9 can be explicitly controlledwhat parts of their modules accessible to the outside world, and which ones hidden and protected from unauthorized access

Modularity brings clarity and order in how applications link to libraries and to each other. Thanks to the module system, dependencies become explicit and manageable.

Let’s consider how working with the module system in Java looks like.

Structure and types of modules

System modules make up Java SE and JDE, providing the basic functionality that every Java application needs. Using the command java --list-modulesyou can see the full list of available system modules, there are a lot of them! Some of them:


Application modules – blocks of own code and dependencies on third-party libraries used by the application.

Automatic modules, is a mechanism that allows existing JAR files to be used as modules, even if they were not specifically intended to run in a module system. Just put the JAR file on the --module-pathand Java will automatically create a module for it, the name of which will be inherited from the name of the JAR file.

Nameless modules, is a lifeline for all code that does not belong to any of the modules module-path. All JAR files downloaded via --class-pathautomatically become part of an unnamed module.

If before everything revolved around class-path — the universal path that the JVM used to search for all classes and libraries, now module-path masthev is used everywhere, offering a stricter and structured division of dependencies and modules.

Definition of the module

Module in Java – this self-sufficient, executable packagewhich contains code and data as well as describes its dependencies on other modules.

File module-info.javamodule center in Java. It defines the module, its dependencies, exported packages, services and provided services. Creating this file is the first step to creating a module:

module com.example.myModule {
    requires java.sql;
    exports com.example.myModule.api;
    uses com.example.myModule.spi.MyService;
    provides com.example.myModule.spi.MyService with com.example.myModule.internal.MyServiceImpl;

requires: indicates that a module depends on another module. Example, requires java.sql; says that a module needs a module java.sql for your work.

exports: makes packages available to other modules, this allows you to control the level of access to application components, improving encapsulation. exports com.example.myModule.api; exports the package, making it available for use outside the module.

uses: indicates that the module uses a specific service. Modules using the service do not necessarily know its implementation. Example:

uses com.example.myModule.spi.MyService;

provides ... with: declares that the module provides an implementation for the service being used. This allows you to create replacement components in the program. provides com.example.myModule.spi.MyService with com.example.myModule.internal.MyServiceImpl; indicates that MyServiceImpl is an implementation MyService.

Consider another example of creating a library module. In this case, we define the module com.example.datawhich requires a number of standard and third-party modules, exports an API for working with data and provides implementation through services:

module com.example.data {
    requires java.logging;
    requires transitive com.example.utils;
    exports com.example.data.api;
    uses com.example.data.spi.DataService;
    provides com.example.data.spi.DataService with com.example.data.internal.DataServiceImpl;

Directive requires transitive com.example.utils; ensures that any module that depends on com.example.dataautomatically gets access to com.example.utils.

Definition of services

A service is an interface or an abstract class, and a service implementation is a concrete class that this interface implements or inherits from an abstract class. Directives are used to declare a service and its implementations uses and provides ... with in the file module-info.java.

Suppose there is an interface ServiceInterfacewhich provides the service, and the class ServiceImplwhich represents the implementation of this service:

package com.example.service;

public interface ServiceInterface {
    void execute();
package com.example.service.impl;

import com.example.service.ServiceInterface;

public class ServiceImpl implements ServiceInterface {
    public void execute() {
        System.out.println("Service executed.");

And now if we have a module com.example.serviceproviderwho provides this service, module-info.java could look like this:

module com.example.serviceprovider {
    exports com.example.service;
    provides com.example.service.ServiceInterface with com.example.service.impl.ServiceImpl;

A module that wants to use this service must declare it using a directive uses in your file module-info.java.

Suppose there is a module com.example.serviceconsumerwho uses this service:

module com.example.serviceconsumer {
    requires com.example.serviceprovider;
    uses com.example.service.ServiceInterface;

At runtime, a module can dynamically discover and use a service implementation using ServiceLoader API. For example, a module com.example.serviceconsumer can execute the following code to use ServiceInterface:

import com.example.service.ServiceInterface;
import java.util.ServiceLoader;

public class ServiceConsumer {
    public static void main(String[] args) {
        ServiceLoader<ServiceInterface> serviceLoader = ServiceLoader.load(ServiceInterface.class);

Example: currency conversion service

For example, there is a module com.example.currencyconverterwhich provides an interface CurrencyConverter for currency conversion. We were told that implementations of this interface could be provided by different modules without changing the code of the module that uses the service.

In the module com.example.currencyconverterwe will define an interface CurrencyConverter:

package com.example.currencyconverter.spi;

public interface CurrencyConverter {
    double convert(double amount, String fromCurrency, String toCurrency);

IN module-info.java of this module, we will declare that it uses the service CurrencyConverter:

module com.example.currencyconverter {
    exports com.example.currencyconverter.spi;
    uses com.example.currencyconverter.spi.CurrencyConverter;

Now suppose we have another module com.example.currencyproviderwhich provides an implementation of this interface. In this module we define a class MyCurrencyConverter:

package com.example.currencyprovider;

import com.example.currencyconverter.spi.CurrencyConverter;

public class MyCurrencyConverter implements CurrencyConverter {
    public double convert(double amount, String fromCurrency, String toCurrency) {
        // Реализация конвертации валют
        return convertedValue;

And in his module-info.java we declare that the module provides a service implementation CurrencyConverter using the class MyCurrencyConverter:

module com.example.currencyprovider {
    requires com.example.currencyconverter;
    provides com.example.currencyconverter.spi.CurrencyConverter with com.example.currencyprovider.MyCurrencyConverter;

When the program starts, the module that uses CurrencyConvertercan get a service implementation and use it without knowing which class provides it. This is done with the help of ServiceLoader:

var serviceLoader = ServiceLoader.load(CurrencyConverter.class);
for (CurrencyConverter converter : serviceLoader) {
    double result = converter.convert(100, "USD", "EUR");
    System.out.println("Converted: " + result);

This approach allows you to add new implementations. CurrencyConverter without changing the code used by the service.

Build and launch

First, a file is created module-info.java at the root of each program module. This file should contain information about the module, including requires, exportsand services uses and provides.

We use the command javac indicating the path to the modules --module-path or -p and natives -d to specify the destination directory. An example command for a module com.example.myapp:

javac -d mods/com.example.myapp --module-path libs --module-source-path src $(find src/com.example.myapp -name "*.java")

This compiles the module into a directory mods/com.example.myapp.

After compilation, use the command jar to create a modular JAR file. Specify the name of the module using the parameter --module-version and file path module-info.class:

jar --create --file=libs/[email protected] --module-version=1.0 -C mods/com.example.myapp/ .

This will create a modular JAR [email protected] in the folder libs.

When starting the program, uses modularity, you must specify the path to the modules using the parameter --module-path or -p.

We specify the main module and class using the parameter --module or -mwhere com.example.myapp/com.example.myapp.Main points to the main class Main in the module com.example.myapp:

java --module-path libs -m com.example.myapp/com.example.myapp.Main

This command will run the application, automatically enabling dependencies between modules.

The Java Platform Module System automatically builds a module graph, allowing dependencies between modules based on information provided in files module-info.java.

All modules are module dependent by default java.basewhich contains core Java classes such as java.lang and java.util. The Java standard library is available to all modules without explicit reference.

The article was prepared on the eve of the launch of the Java Developer specialization. As part of the launch of the specialization, a free webinar on Java multithreading will be held.

Related posts