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.

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

flowchart LR subgraph app_mod["module com.example.app"] app_api["exports com.example.app.api"] end subgraph util_mod["module com.example.util"] util_api["exports com.example.util"] end subgraph model_mod["module com.example.model"] model_api["exports com.example.model"] end app_mod -->|requires| util_mod app_mod -->|requires transitive| model_mod util_mod -->|requires| model_mod

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 / private to individual members.

  • Interfaces — the service interfaces behind uses and provides …​ with.

  • Annotations and Reflection — why opens exists and what breaks without it.