Modules & Module Imports

Java · Lesson 28 of 32 9 min read Updated

Packages group classes, but in a big application even packages are not enough. A library may have public classes that are meant only for its own internal use, yet any code on the class path can call them. Large projects also suffered from missing or duplicated JAR files that were only discovered when the program crashed at run time.

Java 9 introduced the Java Platform Module System (often called JPMS). A module is a named group of packages that states which other modules it needs and which of its packages others may use. The JDK itself is split into about 70 modules. In this lesson you will build a small two-module application, see how modules hide internal code, create a custom runtime with jlink, and use Java 25's new module import declarations.

The JDK is made of modules

Every Java program uses the module java.base, which contains java.lang, java.util, java.io, java.time and the other core packages. Other modules add more: java.sql for databases, java.net.http for the HTTP client, java.desktop for Swing. You can list them and look inside one:

Terminal
java --list-modules
java --describe-module java.sql

After a first line naming the module and its version, the second command prints:

Text
exports java.sql
exports javax.sql
requires java.xml transitive
requires java.logging transitive
requires java.transaction.xa transitive
requires java.base mandated
uses java.sql.Driver

This is a module's contract in a nutshell: it exports two packages for others to use and requires a few other modules. requires transitive means "anyone who requires me can also use these". Every module automatically requires java.base.

Writing your own modules

A module is a folder containing a file named module-info.java at its root, next to its package folders. Here is a project with two modules: a grades library and an app that uses it.

Text
src/
  in.college.grades/
    module-info.java
    in/college/grades/GradeCalculator.java
    in/college/grades/internal/Rules.java
  in.college.app/
    module-info.java
    in/college/app/Main.java

The library exports only its public API package. Its internal package stays hidden:

Java
module in.college.grades {
    exports in.college.grades;
}
Java
package in.college.grades;

import in.college.grades.internal.Rules;

public class GradeCalculator {
    public static String gradeFor(int marks) {
        return Rules.lookup(marks);
    }
}

Rules is a public class with a public lookup method that turns marks into a grade, but because its package in.college.grades.internal is not exported, only code inside this module can use it. The app module declares what it needs:

Java
module in.college.app {
    requires in.college.grades;
}
Java
package in.college.app;

import in.college.grades.GradeCalculator;

public class Main {
    public static void main(String[] args) {
        int[] marks = {94, 81, 47};
        for (int m : marks) {
            System.out.println(m + " -> " + GradeCalculator.gradeFor(m));
        }
        System.out.println("Running in module: " + Main.class.getModule().getName());
    }
}

Compile both modules in one command with --module-source-path, then run with --module-path and -m module/mainClass:

Terminal
javac -d out --module-source-path src -m in.college.app,in.college.grades
java --module-path out -m in.college.app/in.college.app.Main
Output
94 -> A+
81 -> A
47 -> C
Running in module: in.college.app

Module names follow the same reversed-domain style as packages. Each module gets its own folder under out.

Strong encapsulation

Now try to use the hidden class from the app module, with import in.college.grades.internal.Rules;. Even though Rules is public, the compiler refuses:

Output
src\in.college.app\in\college\app\Sneaky.java:3: error: package in.college.grades.internal is not visible
import in.college.grades.internal.Rules;
                        ^
  (package in.college.grades.internal is declared in module in.college.grades, which does not export it)
1 error

This is strong encapsulation: public now means "public to the modules that can read my exported packages", not "public to the whole world". Library authors can change internal code freely without breaking anyone. The JDK uses this to hide its own internals, which is why old code that used sun.misc classes needs updating.

The other module-info.java directives you will meet:

DirectiveMeaning
requires M;this module needs module M
requires transitive M;modules that read me can also read M
exports p;other modules may use public types in package p
exports p to M;only module M may use package p
opens p;allow reflection into package p at run time (used by frameworks such as Hibernate)
uses S; / provides S with C;declare and implement services

Code on the ordinary class path, with no module-info.java, lives in the unnamed module, which can read every other module. That is how all the earlier lessons worked, and it is still how many applications run today. You can adopt modules gradually.

Because the JDK is modular, jlink can build a runtime image that contains only the modules your application needs:

Terminal
jlink --module-path out --add-modules in.college.app --output runtime
runtime/bin/java -m in.college.app/in.college.app.Main

The resulting runtime folder contains just in.college.app, in.college.grades and java.base; on this machine it was about 49 MB instead of the full JDK. You can copy it to a computer with no Java installed and run your program, which is how many desktop tools and container images ship Java today.

Module import declarations in Java 25

Classes from many packages need many import lines. Java 25 adds a way to import everything a module exports in one line.

For larger projects, explicit single-type imports are still common because they show exactly which classes a file uses. Module imports are handy for scripts, small programs and teaching.

Common mistakes

  • Putting module-info.java in the wrong place. It belongs at the root of the module's source folder, next to the top-level package folder.
  • Forgetting to export a package. Other modules then get package ... is not visible. Export only what is meant to be public API.
  • Mixing -cp and --module-path up. Modular code goes on the module path; plain JARs on the class path end up in the unnamed module.
  • Splitting one package across two modules. A package must belong to exactly one module.
  • Expecting reflection to work on non-opened packages. Frameworks need opens for the packages they inspect.

Key points

  • A module is a named set of packages described by module-info.java.
  • requires declares dependencies; exports makes packages usable by other modules; everything else stays hidden.
  • The JDK is modular; every module depends on java.base.
  • Compile with --module-source-path and run with --module-path out -m module/class.
  • jlink builds small custom runtimes containing only the modules you need.
  • Java 25's import module M; imports every exported package of M; single-type and package imports win when names clash.

Exercise

Extend the grades example: add a package in.college.grades.report to the in.college.grades module with a public class ReportCard whose static method line(String name, int marks) returns text such as Meera: 88 (A). Export the new package, use it from Main to print report lines for Meera (88) and Kabir (59), then compile and run both modules again.

Show solution
Java
module in.college.grades {
    exports in.college.grades;
    exports in.college.grades.report;
}
Java
package in.college.grades.report;

import in.college.grades.GradeCalculator;

public class ReportCard {
    public static String line(String name, int marks) {
        return name + ": " + marks + " (" + GradeCalculator.gradeFor(marks) + ")";
    }
}

In Main, add import in.college.grades.report.ReportCard; and print ReportCard.line("Meera", 88) and ReportCard.line("Kabir", 59). Compile and run with the same two commands as before; the new lines are:

Output
Meera: 88 (A)
Kabir: 59 (C)

in.college.app already requires in.college.grades, so it only needs the new package to be exported; no change to the app's module-info.java is needed.

Quick Quiz

1. Which file describes a Java module?

2. A public class sits in a package that its module does not export. Can another module use it?

3. Which module does every other module depend on automatically?

4. In Java 25, how do you fix an ambiguous Date after import module java.base; and import module java.sql;?

Learn Java with a mentor

Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call