Building Modules on the Command Line
When using the module system to create modules for your code, you will likely do that in a project that uses a build tool and so it is its task to get things right.
But it helps tremendously to understand what "right" looks like and how to correctly configure javac, jar, and java to compile, package, and run your application.
This will give you a better understanding of the module system and help debug problems in case the build tool doesn't get it right.
Note: You need to know the module system basics to get the most out of this article. You may also want to check out the description of the core JDK tools.
A Basic Build
Given a project with a few source files, a module declaration, and a few dependencies, this is how you can compile, package, and run it in the simplest way:
There's a bunch of placeholders in there:
$DEPSis the list of dependencies. These are typically paths to JAR files separated by:(Unix) or;(Windows), but on the module path, this can also just be folder names (without the/*-trickery that's required on the class path).$CLASS_FOLDERis the path to the folder where the*.classfiles will be written to.$SOURCESis the list of*.javafiles and must includemodule-info.java.$JARis the path to the JAR file that will be created.$CLASSESis the list of*.classfiles that was created during compilation (thus found in$CLASS_FOLDER) and must includemodule-info.class.$MODULE_NAME/$MAIN_CLASSis the name of the initial module (i.e. the one where module resolution starts) followed by the name of the class containing the app'smainmethod.
For a simple "Hello World" style project with the common src/main/java structure, just a single source file, dependencies in a deps folder, and using Maven's target folder that would look as follows:
Defining a Main Class
The jar option --main-class $MAIN_CLASS embeds $MAIN_CLASS as the class containing the main method in the module descriptor, which allows you to launch a module without having to name the main class:
Note that it is possible to override that class and launch another, simply by naming it as before:
Circumventing Strong Encapsulation
The module system is very strict about access to internal APIs:
If the package isn't exported or opened, access will be denied.
But a package can't just be exported or opened by a module's author - there are also the command line flags --add-exports and --add-opens, which allow the module's user to do that as well.
As an example, see this code that tries to create an instance of the internal class sun.util.BuddhistCalendar:
To compile and run it, we need to use --add-exports:
If the access is reflective...
... compilation will work without further configuration, but we need to add --add-opens when running the code:
Details on strong encapsulation and circumventing it with add-exports and add-opens.
Extending the Module Graph
Starting with an initial set of root modules, the module system computes all of their dependencies and builds a graph, where the modules are nodes and their readability relations are directed edges.
This module graph can be extended with the command line flags --add-modules and --add-reads, which add modules (and their dependencies) and readability edges, respectively.
As an example, let's imagine a project that has an optional dependency on java.sql, but the module is not otherwise required. That means it's not added to the module graph without a little help:
An alternative approach to optional dependencies would be to not list the dependency at all and only add it with --add-modules and --add-reads (this is rarely helpful and not generally recommended - just an example):
Details on extending the module graph with --add-modules and --add-reads.
Last update: September 14, 2021