Packages and Classes in Java
A Java module is a collection of packages, adding one more level of containment above packages, classes, and members.
A Small Tool and a Large Runtime
Imagine a small command-line tool that only reads text files. It uses a handful of standard classes, yet the runtime it runs on carries the entire standard library along with it. Most of that library is never touched by the tool. This mismatch between what a program uses and what it ships with is the problem that Java modules were introduced to address. Java 9 brought modules in to handle the way large Java programs and the platform itself are organized at the largest scale.
What do you think happens?
The tool uses only a handful of standard classes. How much of the standard library does its runtime carry?
Reveal answer
The entire standard library
The runtime carries the whole library even though the tool touches only a small part of it. Modules make it possible to build a smaller runtime, which is covered later in this article.
The central question for this article is this: if code is already grouped into classes and packages, what extra grouping did Java need, and what does that grouping let a developer control? The answer starts with where a module sits in the structure of Java code.
Reading the Containment Chain
A Java module is a collection of packages. It adds one more layer of containment on top of the layers that already existed. Read from the outside inward, the nesting goes: a module holds packages, a package holds classes, and a class holds variables and methods. Each layer contains the one beneath it.
The chain above describes how code is nested, but it does not require every package to be placed in a module. The source is explicit that a package does not have to belong to a module to be used. The standard classes, however, have been divided into a set of modules, so the platform's own code follows the same structure that application developers can now use.
Rethinking What Public Means
Before modules, marking a class public was a broad promise. Any class in any package could use that class, along with its public variables and methods. Modules change the meaning of public for code placed inside a module. Inside the module where a class is defined, a public class is public. Outside that module, it is not automatically visible.
Tracing an Export
The control point is the export. A module can explicitly export a package, and doing so makes the public classes in that package accessible from anywhere. That includes other modules and code that belongs to no module at all. Trace the effect on a module with two packages, one exported and one not. Both contain a public class, but only one of those classes is reachable from outside.
Reaching a public class across a module boundary
A module contains an exported package named api and a non-exported package named internal. Each package has one public class. Can code in another module use each class?
- 1Check the class's package
Service is in api, and Helper is in internal.
- 2Check whether that package is exported
The api package is exported, so its public classes are accessible from anywhere. The internal package is not exported.
- 3Apply the export rule to each class
Service is public and its package is exported, so other code can use it. Helper is public, but its package is not exported, so it stays hidden from outside code.
- 4Notice the role of the keyword
Both classes are declared public, yet only one is reachable. The export decides outside access, not the public keyword alone.
Other modules can use Service but cannot use Helper, even though both are public.
Packages That Stay Effectively Private
Because a package can be left unexported, it can be effectively private. It can serve other packages inside its module while staying invisible to the outside world. In that sense a module hides its internal packages the way a well-designed class hides its implementation details. The module's internal structure becomes a detail that outside code does not need to know about.
Building a Custom Runtime with jlink
The second motivation for modules concerns the size of the standard JRE, which contains all of the standard classes. An application typically uses only a small part of that runtime. Modules make it possible to assemble a smaller custom JRE that includes only the modules the application requires. The JDK provides a jlink command for this purpose.
Once built, the custom runtime can be shipped as a standalone application. People who have not installed a JDK on their machine can still run it. Building it, however, creates responsibilities for the developer. The source is careful to note that the runtime is not portable in the way a single program file might be. Separate runtimes are needed for Windows, macOS, and Linux, just as the JDK itself has platform-specific builds.
Module, Package, and Class Compared
The three layers differ in scope and in what they group together. A module groups packages and sits at the outermost layer. A package groups classes. A class groups variables and methods. The table below summarizes these differences, along with the access role each one plays.
| Layer | What it groups | Where it sits | Access role |
|---|---|---|---|
| Module | Packages | Outermost layer, added by Java modules | Exports decide which packages outside code can reach |
| Package | Classes | Inside a module, or outside any module | Public classes can be reached only if their package is exported |
| Class | Variables and methods | Inside a package | Public means public within the module it belongs to |
Scope and grouping of modules, packages, and classes
Mistakes Beginners Make with Modules
Common mistake Assuming public means accessible from anywhere
What it looks likeA class is declared public inside a module, and the developer expects code in another module to use it without any further setup.
Why it failsInside a module, public is only automatically public within that module. Outside access depends on whether the package is exported.
Do this insteadCheck that the package containing the class is exported, either to everyone or to the named modules that need it.
Common mistake Believing a package must belong to a module to be used
Why it failsThe source states that a package does not have to belong to a module to be used, although the standard classes have been divided into modules.
Common mistake Expecting a jlink runtime to run on every operating system
What it looks likeA developer builds one custom runtime on Linux and plans to ship that same runtime to Windows and macOS users.
Why it failsThe source notes that separate runtimes are needed for Windows, macOS, and Linux.
Do this insteadBuild a separate runtime for each target platform.
Common mistake Thinking a module is the same as a package
Why it failsA module is a collection of packages and sits one layer above them in the containment chain.
Do this insteadTreat a module as the outer container and packages as the units it holds.
Checking Your Reading of Exports
A module contains a package named api, which is exported to everyone, and a package named internal, which is not exported. The internal package has a public class named Helper, which the api package's classes use. Can code in another module use Helper directly? Explain your answer using the idea of exporting.
Hints
- Ask first which package Helper belongs to.
- Ask whether that package is exported, and to whom.
- Remember that public inside a module does not automatically mean visible outside it.
Key Takeaways
- A Java module is a collection of packages. It adds one more layer of containment above packages, classes, and members, and a package does not have to belong to a module to be used.
- Inside a module, public only means public within that module. Outside access is controlled by exporting a package, either to everyone or to specific named modules.
- An unexported package can serve other packages inside its module while remaining invisible outside it, much like a class hides its implementation details.
- The jlink command builds a smaller custom runtime containing only the modules an application needs, which can be shipped and run without a JDK installed.
- A custom runtime is platform-specific, so developers must build separate runtimes for Windows, macOS, and Linux.