Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Modules

Packages sort classes into folders, but they don't stop anyone from using any public class from any package — even one meant as an internal detail. A module (Java 9) is the next level up: a group of packages with a description of which packages it exposes and which other modules it needs. The JDK itself is split into modules too: java.base with the core classes, java.sql for databases, java.net.http with the HTTP client and others.

module-info.java

A module is described by a module-info.java file at the root of its folder. exports opens a package to other modules, and requires declares a dependency. Packages not listed in exports are invisible from outside, even if their classes are public:

// src/shop.core/module-info.java
module shop.core {
    exports shop.core.api;
}

The shop.core module exposes only the shop.core.api package. A module's name usually matches the name of its main package. The java.base module is added automatically; you don't write requires java.base.

A two-module project

Let's make a pricing library shop.core and an application shop.app. Each module lives in its own folder inside src:

src/
├── shop.core/
│   ├── module-info.java
│   └── shop/core/
│       ├── api/PriceCalculator.java
│       └── internal/Discounts.java
└── shop.app/
    ├── module-info.java
    └── shop/app/Main.java

The library's public class uses an internal discounts class:

// src/shop.core/shop/core/api/PriceCalculator.java
package shop.core.api;
 
import shop.core.internal.Discounts;
 
public class PriceCalculator {
    public static double finalPrice(double price, String customer) {
        return price - price * Discounts.percentFor(customer) / 100;
    }
}
// src/shop.core/shop/core/internal/Discounts.java
package shop.core.internal;
 
public class Discounts {
    public static int percentFor(String customer) {
        return customer.equals("Anna") ? 10 : 0;
    }
}

The application declares that it needs shop.core and imports a class from the exported package. A class in a named module must be in a package, so Main here is written in the classic form rather than as a compact file:

// src/shop.app/module-info.java
module shop.app {
    requires shop.core;
}
// src/shop.app/shop/app/Main.java
package shop.app;
 
import shop.core.api.PriceCalculator;
 
public class Main {
    public static void main(String[] args) {
        IO.println("Anna pays " + PriceCalculator.finalPrice(50, "Anna") + " €");
        IO.println("Oleg pays " + PriceCalculator.finalPrice(50, "Oleg") + " €");
    }
}

Compiling and running

javac with the --module-source-path option finds the modules in the src folder, and --module lists which ones to compile. You run with --module-path — the path where the compiled modules are — and name the module and the main class separated by /:

javac -d out --module-source-path src --module shop.core,shop.app
java --module-path out --module shop.app/shop.app.Main

Output:

Anna pays 45.0 €
Oleg pays 50.0 €

The out folder will contain two folders — out/shop.core and out/shop.app — with .class files and the compiled module-info.class.

JDK modules

The java --list-modules command prints all JDK modules, and --describe-module shows the description of one of them:

java --describe-module java.net.http

Output (your version number may differ):

java.net.http@25.0.4.1
exports java.net.http
requires java.base mandated
contains jdk.internal.net.http
contains jdk.internal.net.http.common
contains jdk.internal.net.http.frame
contains jdk.internal.net.http.hpack
contains jdk.internal.net.http.websocket

You can see that the module exports one package and hides five internal ones (contains). In a compact file all JDK modules are available at once, but in your own module you have to declare them: requires java.net.http;, requires java.sql;.

Do you need modules?

Code without module-info.java still works: it goes into the unnamed module and runs with the ordinary -cp (classpath), as in the Packages and access modifiers lesson. Most Spring applications are built this way. Modules are more common in libraries and large systems where the public API must be strictly separated from the internals. Two more words worth recognising: requires transitive passes a dependency on to whoever uses your module, and opens lets libraries such as Jackson access a package's classes through reflection.

Common mistake

If Main imports a class from a package the module doesn't export — for example, import shop.core.internal.Discounts; — the build stops, even though the class is public:

src/shop.app/shop/app/Main.java:3: error: package shop.core.internal is not visible
import shop.core.internal.Discounts;
                ^
  (package shop.core.internal is declared in module shop.core, which does not export it)
1 error

A similar error appears if you forget requires shop.core; in the application's module-info.java — the compiler will say that module shop.app does not read shop.core. The fix is to add the package to exports or the dependency to requires.

Thanks for reading!

Donate to the site — we work hard for you.