← Java: language and application decisions
10 / 10 · 45 MIN

Modules, delivery and diagnosis

Relate APIs, modules, versions and artifacts to interpret compilation and deployment failures.

From a name to permitted access

An import determines how a name can be written in code. It neither installs a JAR nor grants access to an encapsulated package. When a type is unavailable, separate three questions: where are its bytes, which module contains them, and what access does the consumer have? Changing only the import may leave deployment or encapsulation problems intact.

Consider public class p.Item in module inventory.api and a consumer in orders.app. The consumer must read inventory.api, and that module must export p to the consumer. Even when both conditions hold, a private member remains private. Module visibility does not replace member visibility.

Dependence on java.base is implicit for other modules. Fundamental types can therefore be used without repeating requires java.base in every descriptor. Do not generalize that exception: dependence on java.sql, for example, must be addressed according to the code and module graph. Diagnosis should identify the missing relationship rather than open the entire system.

API, reflection and transitive dependencies

exports and opens address different needs. An exported API lets consumers use accessible public types normally. Opening permits runtime reflection, including access not published as a compile-time API. The qualified form, such as opens orders.model to audit.reader, limits the recipient of that opening.

A tool needing to inspect private fields in one package does not by itself justify open module. The decision should retain the requested boundary. Opening every package makes permission broader than the case requires; exporting a package does not automatically grant deep reflective access to its private members.

requires transitive addresses another relationship. If reports.api exposes model.api types in signatures, a transitive dependence can make model.api readable to reports.api consumers. It neither copies classes nor physically re-exports packages. When designing an API, review the types crossing its boundary to decide which dependencies form part of the consumer contract.

Compile for an explicit target

Record compiler version, compilation target and the JVM loading the result separately. All three may differ. A result working in the author’s environment must still match the contracted target. That match includes external JARs shipped with it.

In javac 25, --release 21 selects language rules, class-file target and platform APIs for Java 21. Using only --target does not provide the same restriction on APIs referenced by code. Producing an older header is insufficient when depending on methods the target lacks.

The option neither rewrites precompiled libraries nor certifies all their requirements. If a dependency requires a later JVM, recompiling only the application does not fix deployment. Select a compatible release, rebuild the dependency when feasible, or revise the target through an authorized process. Course snippets use Java SE 25 without preview features; questions about earlier targets state that explicitly.

Launch and artifact set

A deployment description should identify the entry class or module and the artifacts to be searched. The name app.jar alone does not describe every dependency. Keep an unambiguous version list and avoid mixing old and new copies containing the same class. Failure investigation must use the set actually loaded.

In class-based launch, -cp specifies directories and JARs. The separator is a colon on POSIX and a semicolon on Windows. In -jar mode, other class-path settings are ignored: adding -cp to the same command does not automatically include an external library. Deployment may use manifest-declared dependencies, appropriate packaging or class-based launch with a complete class path.

For modules, --module-path identifies where to find modules and -m selects the module and, when supplied, main class. These differ from package names. When reviewing a command, ask which mode is active and which files that mode actually considers. Assessment commands are interpretation material; running them is unnecessary.

Services and discovered implementations

A service interface lets consumers express the needed contract without naming every concrete implementation. ServiceLoader connects that contract to available providers. The interface’s existence guarantees neither an installed provider, correct configuration nor successful instantiation.

An explicit modular consumer uses uses to declare the consumed service and a dependence making the interface accessible when it resides elsewhere. The provider module declares provides ... with .... Class-path deployment instead uses corresponding configuration in META-INF/services. Do not confuse the two mechanisms or assume a class is discovered merely because it implements the interface.

Discovery and instantiation may be lazy. Obtaining a ServiceLoader object does not establish that all providers have loaded. Consumers should handle absence and configuration failures according to their requirements. If an application needs a specific format, having some provider does not establish that the required provider is available; decisions must observe the needed capability.

The error identifies a boundary

UnsupportedClassVersionError points to a class format the JVM does not support. For a class compiled for a later release, identify its origin and compare its target with the actual JVM. Do not automatically blame the main application: the rejected class may come from a library.

NoSuchMethodError describes a different situation. The consumer contains a method reference that the loaded definition no longer provides. If the build used Lib v2 but deployment loaded Lib v1, adding retries does not create the missing method. The useful action reconciles the binary contract with selected artifacts while preserving class-origin evidence.

jdeps helps observe dependencies of inspected classes. Its output does not replace configuration selecting plugin names at runtime or demonstrate successful provider initialization. Combine static reading with deployment data and acceptance criteria. Absence from analysis is a bounded observation, not universal proof that a component is never needed.

Guided review of three descriptors

The example presents three separate module-info.java files. model.api exports model; reports.api transitively depends on model.api and exports reports; reports.app depends on reports.api. Assume the packages exist, exposed types are public and all three modules resolve normally. These are not three declarations to save in one file.

Follow the graph before deciding: reports.app reads reports.api and, through the transitive dependence, model.api. Because model is exported, the consumer can use a public type in that package. If reports.api replaced requires transitive with ordinary requires, that indirect readability would no longer be guaranteed. This matters when a public report signature returns a model type.

This reasoning establishes neither JAR presence on the target nor JVM compatibility. Deployment review must still relate descriptors, selected bytes and runtime. In the final assessment, select the consequence demonstrated by the supplied data while preserving the conclusion’s limits. No answer requires compiling descriptors or implementing a plugin.

// Three separate module-info.java files; illustrative descriptors.
// model.api/module-info.java
module model.api {
    exports model;
}

// reports.api/module-info.java
module reports.api {
    requires transitive model.api;
    exports reports;
}

// reports.app/module-info.java
module reports.app {
    requires reports.api;
}
IN PRACTICE

reports.app reads model.api through reports.api’s transitive dependence. Exported package model makes its public types accessible; private members retain their restrictions.

Common pitfalls

Confusing import with installation; opening every package; treating --target as an API guarantee; ignoring precompiled dependencies; treating static analysis as execution evidence.

Related topics: Objects, interfaces and records · Exceptions, resources and files · Concurrency, state and cancellation

Take this idea with you

A coherent deployment relates names, permissions, versions and actual bytes to the defined target.

Create account

Reference: JLS 25: Packages and modules · Java SE 25; no preview features