Modules & Module Imports
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:
java --list-modules
java --describe-module java.sqlAfter a first line naming the module and its version, the second command prints:
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.DriverThis 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.
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.javaThe library exports only its public API package. Its internal package stays hidden:
module in.college.grades {
exports in.college.grades;
}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:
module in.college.app {
requires in.college.grades;
}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:
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.Main94 -> 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:
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 errorThis 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:
| Directive | Meaning |
|---|---|
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.
Custom runtimes with jlink
Because the JDK is modular, jlink can build a runtime image that contains only the modules your application needs:
jlink --module-path out --add-modules in.college.app --output runtime
runtime/bin/java -m in.college.app/in.college.app.MainThe 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.javain 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
-cpand--module-pathup. 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
opensfor the packages they inspect.
Key points
- A module is a named set of packages described by
module-info.java. requiresdeclares dependencies;exportsmakes packages usable by other modules; everything else stays hidden.- The JDK is modular; every module depends on
java.base. - Compile with
--module-source-pathand run with--module-path out -m module/class. jlinkbuilds 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
module in.college.grades {
exports in.college.grades;
exports in.college.grades.report;
}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:
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?
Each module declares its name, dependencies and exports in module-info.java.
2. A public class sits in a package that its module does not export. Can another module use it?
Modules add strong encapsulation: unexported packages are hidden even if their classes are public.
3. Which module does every other module depend on automatically?
java.base holds the core packages and is required by every module.
4. In Java 25, how do you fix an ambiguous Date after import module java.base; and import module java.sql;?
Single-type imports shadow module imports, so the name becomes unambiguous.
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
