A Java module groups packages and declares which other modules it reads and which packages it exposes. The module system, introduced in Java 9, adds an explicit boundary above ordinary packages. A module descriptor named module-info.java uses requires for dependencies and exports for public API packages. This lesson builds one small named module and explains why classpath, module path, exports, and opens describe different responsibilities. Start with ordinary package and classpath knowledge before adding these boundaries.
A module contains packages, not just one class
The module name is declared in module-info.java and is independent of package names, although consistent naming helps. Public classes are still organized in packages. The descriptor documents the module's intended dependencies and API. A package can contain public implementation classes without being exported to other modules. This gives a library a stronger public boundary than merely relying on naming conventions for internal packages.
Declare readable dependencies with requires
requires names another module your code depends on. Every named module automatically reads java.base, which contains fundamental APIs such as String and java.util, so it does not need an explicit requires java.base statement. Using an API from another platform module requires the appropriate dependency, such as java.logging for java.util.logging. Dependency declaration and library availability must both be correct; requires does not download a missing third-party module.
Expose supported packages with exports
exports makes a package's accessible public types available to modules that read your module. It does not export private members or automatically export every subpackage. A qualified export can name particular consuming modules. Keep implementation packages unexported when clients should not depend on them. Changing an exported public API is a compatibility decision, while unexported internals can generally evolve with fewer external promises.
Use opens for reflective access
opens permits the relevant deep reflective access to a package, while exports serves ordinary compile-time and runtime type access. A framework that inspects private fields may need an opened package even when the public API is already exported. Qualified opens narrows that permission to a specific consumer. Do not open every package solely to silence a configuration error; inspect the framework's documented requirements and expose the smallest intended reflective boundary.
Place named modules on the module path
The module path lets the launcher discover named modules. The -m option selects a module and its main class. The classpath still supports traditional applications through the unnamed module. A JAR placed on the classpath is not automatically launched as a named module. Understanding the selected mode helps diagnose module-not-found and class-not-found messages, which refer to different stages of locating the application.
Keep a first module deliberately small
A single-module project is enough to learn descriptor syntax, package layout, and launch commands. Multiple modules introduce extra source layouts, dependency checks, and packaging choices. Avoid split packages across named modules, and ensure the module descriptor is compiled with its classes. Tools such as jdeps can help inspect dependencies, while jlink can build a tailored runtime image for suitable modules. Those deployment tools come after a working, understandable module boundary.
Practice tasks and checks
Build the two-file module exactly as shown and inspect out for both the descriptor and package directories. Change the module name consistently in module-info.java and the launch command while leaving the package unchanged; the program should still work. Next, add a logging import and use Logger, then declare requires java.logging to make that dependency explicit. Create a second implementation package but do not export it. Explain why another named module should not treat that package as supported API. Finally, distinguish exports from opens in a short design note for a hypothetical reflection-based framework. Do not add an unrestricted opens directive unless the framework actually needs deep access to that package.
Java Summary
- Modules group packages and declare readability and exported APIs.
- exports and opens serve different access mechanisms.
- The module path and classpath are different launch models.
- Continue with package and classpath foundations when these examples are clear.
Java Examples
Each example shows the code, the output it produces and what each part does.
Declare a named module
Use the following java-module commands or configuration.
What to expect: The descriptor declares one exported package and the implicit java.base dependency.
Save this as src/module-info.java. The descriptor names the module org.example.greeting and exports the package with the same text. That naming match is a convention here, not a requirement that every module contain only one package. No explicit dependency is needed for System.out because it comes from java.base. The exports statement will only be meaningful when the package contains the application's compiled classes.
module org.example.greeting {
exports org.example.greeting;
}Add the packaged entry point
Save the packaged source at the path explained below and use the accompanying compile and launch commands.
Expected output after the accompanying package launch commands:
Save this source as src/org/example/greeting/Greeting.java. Its binary class name is org.example.greeting.Greeting. The source declares a package, not the module; module-info.java supplies the enclosing module declaration. The main method remains the familiar Java entry point. Compile both source files together using the next commands so the output contains the descriptor as well as the class.
package org.example.greeting;
public class Greeting {
public static void main(String[] args) {
System.out.println("Named module running");
}
}Named module runningCompile and launch the module
Use the following shell commands or configuration.
Expected output:
Compilation writes module-info.class and the package hierarchy under out. The launcher discovers the module there and selects its main class using the module-name slash class-name form. The text before the slash identifies the module; the qualified name after it identifies the class. Selecting -cp instead would choose a different launch model and would not demonstrate the named-module boundary this example is intended to teach.
javac --release 17 -d out src/module-info.java src/org/example/greeting/Greeting.java
java --module-path out -m org.example.greeting/org.example.greeting.GreetingNamed module runningJava FAQ
Is a package the same as a module?
No. Packages group types and determine qualified class names. Modules group packages and describe dependencies and exposed boundaries. One module can contain several packages, including unexported implementation packages.
Do I need exports for my own module to use its classes?
No. Exports controls access by other modules. Code inside the same module can use its own accessible types according to ordinary Java access rules, whether their packages are exported or not.
Do I need modules for my Java application?
No. Applications on the classpath work as before, and many large projects never add a module-info.java. Modules are most valuable for libraries that want strong boundaries and for building small custom runtimes with jlink.
What is an automatic module?
A plain JAR without a module-info.class that is placed on the module path. Java gives it a module name, from the JAR's manifest or its file name, lets it read every other module and exports all its packages. Automatic modules make it possible to use older libraries from modular code.
What is the unnamed module?
Everything loaded from the classpath belongs to the unnamed module. It can read all other modules, but named modules can't declare a dependency on it. That's how traditional classpath applications keep working on modern Java versions.
Java Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
What is the descriptor filename?
Which module is implicitly read by every named module?
Java Quiz
2 questions to check what you learned.
Which directive exposes a public API package?
- exports
- opens only
- imports
What does the slash separate in the -m launch form?
- Module name and main class name
- Two filesystem drives
- Source and output directory