Packages and Modules
|
This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Java has two nested units of organization. A package groups related types and is the basis for access control and the on-disk layout. A module (since Java 9) groups packages, declares exactly which ones it publishes and which other modules it needs, and is enforced by both the compiler and the runtime. This page follows the Java Tutorials "Packages" lesson and dev.java "Modules"; the normative rules are in JLS chapter 7.
Packages, Imports, and the Classpath
A package declaration must be the first statement in a source file, and its dotted name maps
one-to-one onto a directory path. Reverse-domain names (com.example.util) keep packages globally
unique — see
Naming a Package.
// file: src/com/example/util/Text.java
package com.example.util; // dotted name == directory path
public final class Text {
public static String shout(String s) { return s.toUpperCase() + "!"; }
}
An import lets you use a type by its simple name. A single-type import names one class; an
on-demand (wildcard) import covers every type directly in one package — but not its sub-packages; a
static import brings in a class’s static members. See
Using Package Members and
JLS 7.5.
package com.example.app;
import com.example.util.Text; // single-type import
import java.util.List; // single-type import
import java.util.*; // on-demand: this package only, not sub-packages
import static java.lang.Math.max; // static import: bare member name
import static com.example.util.Text.shout;
public class Main {
public static void main(String[] args) {
List<Integer> xs = new ArrayList<>(List.of(3, 1, 2));
System.out.println(max(xs.get(0), xs.get(1))); // 3
System.out.println(shout("done")); // DONE!
}
}
Types in java.lang (String, Math, System, …) and in the current package need no import. The
compiled .class tree mirrors the package tree, and the classpath is the list of roots the JVM
searches for it — see
Managing Source and Class
Files.
project/
src/
com/example/util/Text.java -> package com.example.util;
com/example/app/Main.java -> package com.example.app;
out/ (compiled tree mirrors the packages)
com/example/util/Text.class
com/example/app/Main.class
javac -d out $(find src -name '*.java')
java -cp out com.example.app.Main
java -cp "out:libs/*" com.example.app.Main # ':' separates roots ( ';' on Windows )
The Four Access Levels
Every member has one of four access levels. The rows below read across package and subclass boundaries;
protected also implies same-package access, and no keyword ("package-private") is the default. See
Controlling Access to Members of
a Class.
Modifier Same class Same package Subclass, other package Anywhere
-----------------------------------------------------------------------------------
public yes yes yes yes
protected yes yes yes no
(no modifier) yes yes no no
private yes no no no
Keep fields private and expose behaviour through methods; use package-private for types that
collaborate closely within one package; reserve protected for members a subclass genuinely needs to
override or call. A public type in a package that its module does not export is still
unreachable from outside that module — module boundaries sit above package access.
The Java Platform Module System
A module is declared in a module-info.java file at the root of its source tree. It states the
module’s name, the modules it requires, and the packages it exports. The compiler and runtime then
enforce strong encapsulation: a package that is not exported cannot be referenced from another
module, even if its types are public. See
the Packages lesson and the
java.base module
summary.
// file: src/com.example.app/module-info.java
module com.example.app {
requires com.example.util; // needed to compile and to run
requires transitive java.sql; // modules that read us also read java.sql
requires static org.jspecify; // needed to compile only (e.g. annotations)
exports com.example.app.api; // public types here are visible to any module
exports com.example.app.spi to com.example.plugins; // qualified: only to that module
opens com.example.app.model; // allow deep reflection at run time
opens com.example.app.dto to com.fasterxml.jackson.databind;
uses com.example.app.spi.Codec; // this module consumes a service...
provides com.example.app.spi.Codec
with com.example.app.internal.JsonCodec; // ...and this class supplies one
}
exports grants compile-time and run-time access to a package’s public API. opens additionally
permits reflective access to all members (frameworks that inject or serialize need it); an open
package is not otherwise more visible. requires transitive re-exports a dependency so consumers do
not have to name it themselves. The uses / provides … with pair is the module-aware form of
java.util.ServiceLoader.
# compile and run on the module path
javac -d out/com.example.app $(find src/com.example.app -name '*.java')
java --module-path out --module com.example.app/com.example.app.Main
# a modular JAR, then launch it by module name
jar --create --file app.jar --main-class com.example.app.Main -C out/com.example.app .
java --module-path app.jar --module com.example.app
The module path (--module-path / -p) carries modules; the classpath carries everything else. A
plain JAR placed on the module path becomes an automatic module: its name is derived from the
Automatic-Module-Name manifest header or the file name, it reads every other module, and it exports
all of its packages. Code loaded from the classpath lives in the unnamed module, which reads
everything but is not readable by any named module. Migrate bottom-up — put libraries on the module
path as automatic modules first, and add your own module-info.java last.
Packaging a runtime: jlink and jpackage
jlink assembles a custom
runtime image containing only the modules your application resolves, and
jpackage wraps that image in
a platform-native installer or app image. Both are covered alongside the other JDK tools in
Build and Tooling.
jlink --add-modules com.example.app --output dist/runtime --strip-debug --compress zip-6
jpackage --name MyApp --type dmg \
--module com.example.app/com.example.app.Main --runtime-image dist/runtime
A three-module graph
com.example.app names com.example.util in its requires, and reaches com.example.model without
naming it because com.example.util requires it transitive.
See Also
-
Build and Tooling —
javac,jar,jdeps,jlink, and the Maven/Gradle layout that produces the class tree. -
Classes and Objects — applying
public/protected/privateto individual members. -
Interfaces — the service interfaces behind
usesandprovides … with. -
Annotations and Reflection — why
opensexists and what breaks without it.