# Modules
Modules are defined in a file named module-info.java , named a module descriptor. It should be placed in the source-code root:
Here is a simple module descriptor:
The module name should be unique and it is recommended that you use the same Reverse-DNS naming notation
(opens new window) as used by packages to help ensure this.
The module java.base , which contains Java’s basic classes, is implicitly visible to any module and does not need to be included.
The requires declaration allows us to use other modules, in the example the module java.httpclient is imported.
A module can also specify which packages it exports and therefore makes it visible to other modules.
The package com.example.foo declared in the exports clause will be visible to other modules. Any sub-packages of com.example.foo will not be exported, they need their own export declarations.
Conversely, com.example.bar which is not listed in exports clauses will not be visible to other modules.
# Syntax
- requires java.xml;
- requires public java.xml; # exposes module to dependents for use
- exports com.example.foo; # dependents can use public types in this package
- exports com.example.foo.impl to com.example.bar; # restrict usage to a module
# Remarks
The use of modules is encouraged but not required, this allows existing code to continue working in Java 9. It also allows for a gradual transition to modular code.
Any non-modular code is put in an unnamed module when it is compiled. This is a special module that is able to use types from all other modules but only from packages which have an exports declaration.
All packages in the unnamed module are exported automatically.
Introduction to Modules in Java
The Java APIs are organized into methods, classes, packages, and — at the highest level — modules. A module has a number of essential pieces of information attached to it:
- a name
- a list of dependencies on other modules
- a public API (with everything else being module internal and inaccessible)
- a list of services it uses and provides
Not only the Java APIs come with these information, you also have the option to create modules for your own projects. By deploying your project as modules, you increase reliability and maintainability, prevent accidental use of internal APIs, and can more easily create runtime images that contain just the JDK code you need (with the option to include your own app in the image to make it standalone).
Before we come to these benefits, we will explore how to define a module and its properties, how to turn it into a deliverable JAR, and how the module system handles them. To make that easier, we're going to assume that everything (code in the JDK, libraries, frameworks, apps) is a module — why that is not necessary and how modules can be created incrementally is covered in dedicated articles.
Module Declarations
At the core of each module is the module declaration, a file with the name module-info.java that defines all properties of a module. As an example, here's the one for java.sql , the platform module that defines the JDBC API:
It defines the module's name (java.sql), its dependencies on other modules ( java.logging , java.transaction.xa , java.xml ), the packages that make up its public API ( java.sql and javax.sql ), and which services it uses ( java.sql.Driver ). This module already employs a more refined form to define dependencies by adding the keyword transitive but does of course not use all capabilities of the module system. Generally speaking, a module declaration has the following basic form:
(The entire module can be open and the requires , exports , and opens directives can be further refined, but those are topics for later.)
You can create module declarations for your projects. Their recommended location — where all tools will pick them up the easiest — is in a project's source root folder, i.e. the folder containing your package directories, often src/main/java . For a library, a module declaration might look like this:
For an app, it might be something like this:
Let's quickly go over the details. This section focuses on what needs to go into the module declaration:
- module name
- dependencies
- exported packages
- used and provided services
The effects are discussed in a later section.
Module Name
Module names have the same requirements and guidelines as package names:
- legal letters include A — Z , a — z , 0 — 9 , _ , and $ , separated by .
- by convention module names are all lower case and $ is only used for mechanically generated code
- names should be globally unique
In the earlier example, the JDK module's declaration started with module java.sql , which defined the module with the name java.sql. The two custom modules were named com.example.lib and com.example.app .
Given a JAR, the corresponding module name can be inferred from the project's documentation, with a look into the module-info.class file in the JAR (more on that later), with the help of an IDE, or by running jar —describe-module —file $FILE against the JAR file.
Regarding module name uniqueness, the recommendation is the same as for packages: Pick a URL that's associated with the project and invert it to come up with the first part of the module name, then refine from there. (This implies that the two example modules are associated with the domain example.com.) If you apply this process to module names and package names, the former will usually be a prefix of the latter because modules are more general than packages. That is by no means required but an indicator that the names were chosen well.
Requiring Dependencies
The requires directives list all direct dependencies by their module names. Take a look at those from the three modules above:
We can see that the app module com.example.app depends on the library com.example.lib, which in turn needs the unrelated module com.sample.other and the platform module java.sql. We don't have the declaration of com.sample.other but we know that java.sql depends on java.logging, java.transaction.xa, and java.xml. If we look those up, we see that they have no further dependencies. (Or rather, no explicit dependencies — check the section on the base module below for more details.)
It is also possible to handle optional dependencies (with requires static ) and to "forward" dependencies that are part of a module's API (with requires transitive ), but that's covered in separate articles.
The list of external dependencies is in all likelihood very similar to the dependencies listed in your build configuration. This often leads to the question whether this is redundant and should be auto-generated. It's not redundant because a module name contains no version or really any other information that a build tool needs to obtain a JAR (like group ID and artifact ID) and build configurations list those information but not a module's name. Because the module name can be inferred given a JAR it is possible to generate this section of module-info.java . It's not clear whether that's worth the effort, though, particularly with the added complexity of dependencies on platform modules and of static and transitive modifiers as well as the fact that IDEs already propose adding requires directives when they're needed (much like package imports), which makes updating module declarations very simple.
Exporting And Opening Packages
By default all types, even public ones, are only accessible inside a module. To give code outside of a module access to a type, the package containing the type needs to be either exported or opened. This is achieved by using the exports and opens directives, which include the name of a package the module contains. The exact effect of exporting is discussed in the section on strong encapsulation below, of opening in an article on reflection, but the gist is:
- public types and members in exported packages are available at compile and run time
- all types and members in open packages can be accessed at run time via reflection
Here are the exports and opens directives from the three example modules:
This shows that java.sql exports a package of the same name as well as javax.sql — the module contains a lot more packages of course, but they are not part of its API and don't concern us. The library module exports two packages to be used by other modules — again, all other (potential) packages are safely locked away. The app module exports no packages, which isn't uncommon as the module launching the application is rarely a dependency of other modules and so nobody calls into it. It does open com.example.app.entities for reflection though — judging by the name probably because it contains entities that other modules want to interact with via reflection (think JPA).
There are also qualified variants of the exports and opens directives that allow you to export/open a package to specific modules only.
As a rule of thumb, try to export as few packages as possible — just like keeping fields private, only making methods package-visible or public if needed, and making classes package-visible by default and only public if needed in another package. This reduces the amount of code that is visible elsewhere, which reduces complexity.
Using And Providing Services
Services are their own topic — for now it suffices to say that you can use them to decouple the user of an API from its implementation, making it easier to replace it as late as when launching the application. If a module uses a type (an interface or a class) as a service, it needs to state that in the module declaration with the uses directive, which includes the fully qualified type name. Modules providing a service express in their module declaration which of their own types do that (usually by implementing or extending it).
The library and app example modules show the two sides:
The lib module uses Service , one of its own types, as a service and the app module, which depends on the lib module, provides it with MyService . At run time the lib module will then access all classes that implement / extend the service type by using the ServiceLoader API with a call as simple as ServiceLoader.load(Service.class) . That means the library module executes behavior defined in the app module even though it doesn't depend on it — that's great to untangle dependencies and keep modules focused in their concerns.
Building and Launching Modules
The module declaration module-info.java is a source file like any other and so it takes a few steps before it's running in the JVM. Fortunately, these are the exact same steps that your source code takes and most build tools and IDEs understand that well enough to adapt to its presence. In all likelihood, you don't need to do anything manually to build and launch a modular code base. Of course there is value in understanding the nitty, gritty details, so a dedicated article takes you from source code to running JVM with just command line tools.
Here, we'll stay on a higher level of abstraction and instead discuss a few concepts that play an important role in building and running modular code:
- modular JARs
- module path
- module resolution and module graph
- the base module
Modular JARs
A module-info.java file (aka module declaration) gets compiled to module-info.class (called module descriptor), which can then be placed into a JAR's root directory or in a version-specific directory, if it's a multi-release JAR. A JAR containing a module descriptor is called a modular JAR and is ready to be used as a module — JARs without a descriptor are plain JARs. If a module JAR is placed on the module path (see below), it becomes a module at run time, but it can still be used on the class path as well, where it becomes part of the unnamed module just like plain JARs on the class path.
Module Path
The module path is a new concept that parallels the class path: It is a list of artifacts (JARs or bytecode folders) and directories that contain artifacts. The module system uses it to locate required modules that are not found in the runtime, so usually all app, library, and framework modules. It turns all artifacts on the module path into modules, even plain JARs, which get turned into automatic modules, which is enables incremental modularization. Both javac and java as well as other module-related commands understand and process the module path.
Side note: This and the previous section together have revealed a possibly surprising behavior of the module system: Whether a JAR is modular or not does not determine whether it is treated as a module! All JARs on the class path are treated as a single barely-a-module, all JARs on the module path get turned into modules. That means the person in charge of a project gets to decide which dependencies end up as individual modules and which don't (as opposed to the dependencies' maintainers).
Module Resolution and Module Graph
To launch a modular app, run the java command with a module path and a so-called initial module — the module that contains the main method:
This will start a process called module resolution: Beginning with the initial module's name, the module system will search the module path for it. If it finds it, it will check its requires directives to see what modules it needs and then repeats the process for them. If it doesn't find a module, it will throw an error then and there, letting you know a dependency is missing. You can observe this process by adding the command line option —show-module-resolution .
The result of this process is the module graph. Its nodes are modules, its edges are a bit more complicated: Each requires directive spawns an edge between the two modules, called a readability edge where the requiring module reads the required one. There are other ways to create edges but that doesn't need to concern us now because it doesn't change anything fundamental.
If we imagine an average Java program, for example a backend for a web app, we can picture it's module graph: At the top we'll find the initial module, further down the other app modules as well as the frameworks and libraries they use. Then come their dependencies and at some point the JDK modules with java.base at the bottom — read on for details on that.
Base Module
There's one module to rule them all: java.base, the so-called base module. It contains classes like Class and ClassLoader , packages like java.lang and java.util , and the entire module system. Without it, a program on the JVM can't function and so it gets special status:
- the module system is aware of it specifically
- no need to put requires java.base in a module declaration — a dependency on the base module comes for free
So when an earlier section discussed the dependencies of the various modules, that wasn't entirely complete. They all also implicitly depend on the base module — because they have to. And when the previous section said that the module system starts with module resolution, that's not 100% correct either. The first thing that happens is that, in a profound chicken-and-egg head-scratcher, the module system resolves the base module and bootstraps itself.
Module System Benefits
So what do you get for your troubles of creating module declarations for your projects? Here are the three most prominent benefits:
- strong encapsulation
- reliable configuration
- scalable platform
Strong Encapsulation
Without modules, every public class or member is free to be used by any other class — no way to make something visible within a JAR but not beyond it's boundaries. And even non-public visibility isn't really a deterrent because there's always reflection that can be used to break into private APIs. The reason is that JARs have no boundaries, they are just containers for the class loader to load classes from.
Modules are different, they do have a boundary that compiler and runtime recognize. A type from a module can only be used if:
- the type is public (as before)
- the package is exported
- the module using the type reads the module containing the type
That means the creator of a module has much more control over which types make up the public API. No longer is it all public types, now it's all public types in exported packages, which finally allows us to lock away functionality that contains public types that are not supposed to be used outside a sub-project.
This is obviously crucial for the JDK APIs itself, whose developers no longer need to plead with us not to use packages like sun.* or com.sun.* (for more on what happened to those internal APIs and why you can still use sun.misc.Unsafe , see this article on strong encapsulation of JDK internals). The JDK also no longer needs to rely on the security manager's manual approach to prevent access to security-sensitive types and methods, thus eliminating an entire class of potential security hazards. Libraries and frameworks can also benefit from clearly communicating and enforcing which APIs are meant to be public and (presumably) stable and which are internal.
Application code bases small and large can be sure not to accidentally use internal APIs of their dependencies that my change in any patch release. Larger code bases can further benefit from creating multiple modules with strong boundaries. That way, developers that implement a feature can clearly communicate to their colleagues which parts of the added code are meant for use in other parts of the app and which are just internal scaffolding — no more accidental use of an API that "was never intended for that use case".
All of that said, if you absolutely have to use internal APIs, of the JDK or other modules, you still can with these two command line flags, assuming you're in control of the application's launch command.
Reliable Configuration
During module resolution, the module system checks whether all required dependencies, direct and transitive, are present and reports an error if something's missing. But it goes beyond just checking presence.
There must be no ambiguity, i.e. no two artifacts can claim they're the same module. This is particularly interesting in the case where two versions of the same module are present. Because the module system has no concept of versions (beyond recording them as a string), it treats this as a duplicate module. Accordingly, it reports an error if it runs into this situation.
There must be no static dependency cycles between modules. At run time, it's possible and even necessary for modules to access each other (think about code using Spring annotations and Spring reflecting over that code), but these must not be compile dependencies (Spring is obviously not compiled against the code it reflects over).
Packages should have a unique origin, so no two modules can contain types in the same package. If they do, this is called a split package, and the module system will refuse to compile or launch such configurations.
This verification isn't airtight of course and it's possible for problems to hide long enough to crash a running application. If, for example, the wrong version of a module ends up in the right place, the application will launch (all required modules are present) but will crash later, when, for example, a class or method is missing. It does detect a number of common problems early, though, reducing the chance that an application that launched will fail at run time because of dependency issues.
Scalable Platform
With the JDK split into modules for everything from XML handling to JDBC API, it is finally possible to hand-craft a runtime image that contains just the JDK features you need and ship that with your app. If your code base is fully modularized, you can go one step further and include your modules in that image, making it a self-contained application image that comes with everything it needs, from your code to dependencies to JDK APIs and the JVM. This article explains how to do that.
Further Reading
You now have a basic understanding of the workings and benefits of the module system and are ready to explore many more topics to deepen your knowledge. The following articles don't have to be read in order — each mentions right at the beginning which others you should've read before.
Java Modules
A Java module is a packaging mechanism that enables you to package a Java application or Java API as a separate Java module. A Java module is packaged as a modular JAR file. A Java module can specify which of the Java packages it contains that should be visible to other Java modules which uses this module. A Java module must also specify which other Java modules is requires to do its job. This will be explained in more detail later in this Java modules tutorial.
Java modules is a new feature in Java 9 via the Java Platform Module System (JPMS). The Java Platform Module System is also sometimes referred to as Java Jigsaw or Project Jigsaw depending on where you read. Jigsaw was the internally used project name during development. Later Jigsaw changed name to Java Platform Module System.
Java Module Benefits
The Java Platform Module System brings several benefits to us Java developers. I will list the biggest benefits below.
Smaller Application Distributables via the Modular Java Platform
As part of Project Jigsaw, all the Java Platform APIs have been split up into separate modules. The benefit of splitting all the Java APIs up into modules is that you can now specify what modules of the Java platform your application requires. Knowing what Java Platform modules your application requires, Java can package up your application including only the Java Platform modules that your application actually uses.
Before Java 9 and the Java Platform Module System you would have had to package all of the Java Platform APIs with your Java application because there was no official way of reliably checking what classes your Java application used. Since the Java Platform APIs have grown quite large over the years, your application would get a large amount of Java classes included in its distribution, many of which your application would probably not be using.
The unused classes makes your application distributable bigger than it needs to be. This can be a problem on small devices like mobile phones, Raspberry Pis etc. With the Java Platform Module System you can now package your application with only the modules of the Java Platform APIs that your application is actuallly using. This will result in smaller application distributables.
Encapsulation of Internal Packages
A Java module must explicitly tell which Java packages inside the module are to be exported (visible) to other Java modules using the module. A Java module can contain Java packages which are not exported. Classes in unexported packages cannot be used by other Java modules. Such packages can only be used internally in the Java module that contains them.
Packages that are not exported are also referred to as hidden packages, or encapsulated packages.
Startup Detection of Missing Modules
From Java 9 and forward, Java applications must be packaged as Java modules too. Therefore an application module specifies what other modules (Java API modules or third party modules) it uses. Therefore the Java VM can check the whole module dependency graph from the application module and forward, when the Java VM starts up. If any required modules are not found at startup, the Java VM reports the missing module and shuts down.
Before Java 9 missing classes (e.g. from a missing JAR file) would not be detected until the application actually tried to use the missing class. This would happen sometime at runtime — depending on when the application tried to use the missing class.
Having missing modules reported at application startup time is a big advantage compared to at runtime when trying to use the missing module / JAR / class.
Java Module Basics
Now you know what a Java module is and what the benefits of Java modules are, let us take a look at the basics of Java modules.
Modules Contain One or More Packages
A Java module is one or more Java packages that belong together. A module could be either a full Java application, a Java Platform API, or a third party API.
Module Naming
A Java module must be given a unique name. For instance, a valid module name could be
A Java module name follows the same naming rules as Java packages. However, you should not use underscores ( _ ) in module names (or package names, class names, method names, variable names etc.) from Java 9 and forward, because Java wants to use underscore as a reserved identifier in the future.
It is recommended to name a Java module the same as the name of the root Java package contained in the module — if that is possible (some modules might contain multiple root packages).
Module Root Directory
Before Java 9 all Java classes for an application or API were nested directly inside a root class directory (which was added to the classpath), or directly inside a JAR file. For instance, the directory structure for the compiled packages of com.jenkov.mymodule would look like this:
A little more graphically, it would look like this:
- com
- jenkov
- mymodule
From Java 9 the Java Platform Module System offers an alternative directory structure which can make it easier to compile Java sources. From Java 9 a module can be nested under a root directory with the same name as the module. In the example above we have a directory structure for a package named com.jenkov.mymodule . This Java package is to be contained within a Java module with the same name ( also com.jenkov.mymodule ).
The directory structure for the above Java package contained in a Java module of the same name, would look like this:
A little more graphically, it would look like this:
- com.jenkov.mymodule
- com
- jenkov
- mymodule
Notice the fullstops ( . ) in the module root directory name. These fullstops need to be there because they are part of the module name! They are not to be interpreted as subdirectory path dividers!
The module root directory is used both for the source files and compiled classes of a Java module. That means, that if your Java project has a source root directory named src/main/java — then each module inside your project will have its own module root directory under src/main/java . For instance:
The same directory structure would be seen in the Java compiler’s output directory.
It is common to only have one Java module per project. You will still need the module root directory in that case, but the source and compiler output root directories will only contain a single module root directory.
In the section Compiling a Java Module I explain how the above module root directory structure can make it easier to compile all Java sources for a Java module.
Module Descriptor (module-info.java)
Each Java module needs a Java module descriptor named module-info.java which has to be located in the corresponding module root directory. For the module root directory src/main/java/com.jenkov.mymodule the path to the module’s module descriptor will be src/main/java/com.jenkov.mymodule/module-info.java .
The module descriptor specifies which packages a module exports, and what other modules the module requires. These details will be explained in the following sections. Here is how a basic, empty Java module descriptor looks:
First is the module keyword, followed by the name of the module, and then a set of curly brackets. The exported packages and required modules will be specified inside the curly brackets.
Notice also how the module descriptor is suffixed .java and yet it uses a hyphen in the file name ( module-info.java ). Hyphens are not normally allowed in Java class names, but in module descriptor file names they are required!
Module Exports
A Java module must explicitly export all packages in the module that are to be accessible for other modules using the module. The exported packages are declared in the module descriptor. Here is how a simple export declaration looks inside a module descriptor:
This example exports the package called com.jenkov.mymodule .
Please note, that only the listed package itself is exported. No «subpackages» of the exported package are exported. That means, that if the mymodule package contained a subpackage named util then the com.jenkov.mymodule.util package is *not* exported just because com.jenkov.mymodule is.
To export a subpackage also, you must declare it explicitly in the module descriptor, like this:
You do not have to export the parent package in order to export a subpackage. The following module descriptor exports statement is perfectly valid:
This example only exports the com.jenkov.mymodule.util package, and not the com.jenkov.mymodule package.
Module Requires
If a Java module requires another module to do its work, that other module must be specified in the module descriptor too. Here is an example of a Java module requires declaration:
This example module descriptor declares that it requires the standard Java module named javafx.graphics .
Circular Dependencies Not Allowed
It is not allowed to have circular dependencies between modules. In other words, If module A requires module B, then module B cannot also require module A. The module dependency graph must be an acyclic graph.
Split Packages Not Allowed
The same Java package can only be exported by a single Java module at runtime. In other words, you cannot have two (or more) modules that export the same package in use at the same time. The Java VM will complain at startup if you do.
Having two modules export the same package is also sometimes referred to as a split package. By split package is meant that the total content (classes) of the package is split between multiple modules. This is not allowed.
Compiling a Java Module
In order to compile a Java module you need to use the javac command that comes with the Java SDK. Remember, you need Java 9 or later to compile a Java module.
Remember you must have the javac command from the JDK installation on your path (environment variable) for this command to work. Alternatively you can replace the javac part in the command above with the full path to where the javac command is located, like this:
It is also possible to compile a Java module with Ant. You can see that in my tutorial about how to Build Java Modules With Ant.
When javac compiles the module it writes the compiled result into the directory specified after the -d argument to the javac command. Inside that directory you will find a directory with the name of the module, and inside that directory you will find the compiled classes plus a compiled version of the module-info.java module descriptor named module-info.class .
The —module-source-path should point to the source root directory, not the module root directory. The source root directory is normally one level up from the module root directory.
The —module argument specifies which Java module to compile. In the example above it is the module named com.jenkov.mymodule . You can specify multiple modules to be compiled by separating the module names with a comma. For instance:
Running a Java Module
In order to run the main class of a Java module you use the java command, like this:
The —module-path argument points to the root directory where all the compiled modules are located. Remember, this is one level above the module root directory.
The —module argument tells what module + main class to run. In the example the module name is the com.jenkov.mymodule part and the main class name is the com.jenkov.mymodule.Main . Notice how the module name and main class name are separated by a slash ( / ) character.
Building a Java Module JAR File
You can package a Java module inside a standard JAR file. You do so with the standard jar command that comes with the Java SDK. The package directory hierarchy must start at the root of the JAR file, just like for pre Java 9 JAR files. Additionally, a Java module JAR file contains a compiled version of the module descriptor at the root of the JAR file.
Here is the jar command needed to generate a JAR file from a compiled Java module:
The -c argument tells jar to create a new JAR file.
The —file argument tells the path of the output file — the created JAR file. Any directories you want the output JAR file to be under must already exist!
The -C (uppercase C) argument tells the jar command to change directory to out/com.jenkov.javafx (the compiled module root directory) and then include everything found in that directory — due to the following . argument (which signals «current directory»).
It is also possible to package a Java module with Ant. You can see that in my tutorial about how to Build Java Modules With Ant.
Setting the JAR Main Class
You can still set the JAR main class when generating the module JAR file. You do so by providing a —main-class argument. Here is an example of setting the main class of a Java module JAR file:
You can now run the main class of this JAR file with a shortcut. This shortcut will be explained in the next section.
Running a Java Module From a JAR
Once you have packaged your Java module into a JAR file, you can run it just like running a normal module. Just include the module JAR file on the module path. Here is how you run the main class from a Java module JAR file:
For this command to work the module JAR file must be located in the out-jar directory.
Running a Java Module From a JAR With a Main Class Set
If the Java module JAR file has a main class set (see a few sections earlier in this tutorial for how to do that), you can run the Java module main class with a little shorter command line. Here is an example of running a Java module from a JAR file with a main class set:
Notice how no —module-path argument is set. This requires that the Java module does not use any third party modules. Otherwise you should provide a —module-path argument too, so the Java VM can find the third party modules your module requires.
Packing a Java Module as a Standalone Application
You can package a Java module along with all required modules (recursively) and the Java Runtime Environment into a standalone application. The user of such a standalone application does not need to have Java pre-installed to run the application, as the application comes with Java included. That is, as much of the Java platform as the application actually uses.
You package a Java module into a standalone application using the jlink command which comes with the Java SDK. Here is how you package a Java module with jlink :
The —module-path argument specifies the module paths to look for modules in. The example above sets the out directory into which we have previously compiled our module, and the jmods directory of the JDK installation.
The —add-modules argument specifies the Java modules to package into the standalone application. The example above just includes the com.jenkov.mymodule module.
The —output argument specifies what directory to write the generated standalone Java application to. The directory must not exist already.
Running the Standalone Application
Once packaged, you can run the standalone Java application by opening a console (or terminal), change directory into the standalone application directory, and execute this command:
The standalone Java application contains a bin directory with a java executable in. This java executable is used to run the application.
The —module argument specifies which module plus main class to run.
Unnamed Module
From Java 9 and forward, all Java classes must be located in a module for the Java VM to use them. But what do you do with older Java libraries where you just have the compiled classes, or a JAR file?
In Java 9 you can still use the -classpath argument to the Java VM when running an application. On the classpath you can include all your older Java classes, just like you have done before Java 9. All classes found on the classpath will be included in what Java calls the unnamed module.
The unnamed module exports all its packages. However, the classes in the unnamed module are only readable by other classes in the unnamed module — or from automatic modules (see next section). No named module can read the classes of the unnamed module.
If a package is exported by a named module, but also found in the unnamed module, the package from the named module will be used.
All classes in the unnamed module requires all modules found on the module path. That way, all classes in the unnamed module can read all classes exported by all the Java modules found on the module path.
Automatic Modules
What if you are modularizing your own code, but your code uses a third party library which is not yet modularized? While you can include the third party library on the classpath and thus include it in the unnamed module, your own named modules cannot use it, because named modules cannot read classes from the unnamed module.
The solution is called automatic modules. An automatic module is made from a JAR file with Java classes that are not modularized, meaning the JAR file has no module descriptor. This is the case with JAR files developed with Java 8 or earlier. When you place an ordinary JAR file on the module path (not the classpath) the Java VM will convert it to an automatic module at runtime.
An automatic module requires all named modules on the module path. In other words, it can read all packages exported by all named modules in the module path.
If your application contains multiple automatic modules, each automatic module can read the classes of all other automatic modules.
An automatic module can read classes in the unnamed module. This is different from explicitly named modules (real Java modules) which cannot read classes in the unnamed module.
An automatic module exports all its packages, so all named modules on the module path can use the classes of an automatic module. Named modules still have to explicitly require the automatic module though.
The rule about not allowing split packages also counts for automatic modules. If multiple JAR files contain (and thus exports) the same Java package, then only one of these JAR files can be used as an automatic module.
An automatic module is a named module. The name of an automatic module is derived from the name of the JAR file. If the name of the JAR file is com-jenkov-mymodule.jar the corresponding module name will be com.jenkov.mymodule . The — (dash) characters are not allowed in a module name, so they are replaced with the . character. The .jar suffix is removed.
If a JAR file contains versioning in its file name, e.g. com-jenkov-mymodule-2.9.1.jar then the versioning part is removed from the file name too, before the automatic module name is derived. The resulting automatic module name is thus still com.jenkov.mymodule .
Services
With Java 9 comes a new concept called services. Java services is related to the Java Platform Module System, so I will explain Java services in this Java module tutorial.
A service consists of two major parts:
- A service interface.
- One or more service implementations.
The service interface is typically located in a service interface Java module which only contains the service interface, plus any classes and interfaces related to the service interface.
The service implementations are provided by separate Java modules — not the service interface module. Typically a service implementation Java module will contain a single service implementation.
A Java module or application can require the service interface module and code against the service interface, without knowing exactly which other module delivers the service implementation. The service implementation is discovered at runtime, and depends on what service implementation modules are available on the Java module path when the application is launched.
Service Interface Module
Java service interface modules do not require a special declaration of the service interface. You just create a regular Java module. Here is a Java service module descriptor example:
Notice how the actual service interface is not mentioned. The service interface module only exports the Java package that contains the service interface. The service interface is just a normal Java interface, so I have not shown an example of it.
Service Implementation Module
A Java module that wants to implement a service interface from a service interface module must:
- Require the service interface module in its own module descriptor.
- Implement the service interface with a Java class.
- Declare the service interface implementation in its module descriptor.
Imagine that the com.jenkov.myservice module contains an interface named com.jenkov.myservice.MyService . Imagine too, that you want to create a service implementation module for this service interface. Imagine that your implementation is called com.blabla.myservice.MyServiceImpl . To declare that service implementation the module descriptor for the service implementation module would have to look like this:
The module descriptor first declares that it requires the service interface module. Second, the module descriptor declares that it provides an implementation for the service interface com.jenkov.myservice.MyService via the class com.blabla.myservice.MyServiceImpl .
Now that this module declares that it implements the service interface, we need to see how a Java module can lookup an implementation of the service interface at runtime.
Service Client Module
Once you have both a service interface module and a service implementation module, you can create a client module that uses the service. Sometimes a service client module is referred to as a service consumer module or service user module, but the meaning is the same — a module that uses a service specified in an external module and implemented by yet another external module.
In order to use the service, the client module must declare in its module descriptor that it uses the service. Here is how to declare the use of a service in a module descriptor:
Notice how the client module descriptor also declares that it requires the com.jenkov.myservice module which contains the service interface. It does not need to require the service implementation modules. Those are looked up at runtime. Only the service interface module must be required.
The advantage of not having to declare the service implementation modules is, that the implementation modules can be exchanged without breaking the client code. You can decide what service implementation to use when assembling the application — by dropping the desired service implementation module(s) onto the module path. The client module, and service interface module, are thus decoupled from the service implementation modules.
Now the service client module can lookup a service interface implementation at runtime like this:
The returned Iterator contains a list of implementations of the MyService interface. In fact, it will contain all the implementations found in the modules found on the module path. The client module can now iterate the Iterator and find the service implementation it wants to use.
Module Versioning
The Java Platform Module System does not support versioning of Java modules. You cannot have multiple versions of a module available on the module path when running a Java 9+ application. You will need to use a build tool like Maven or Gradle to handle versioning of your module, and the external modules your module depends on (uses / requires).
Multi Java Version Module JAR Files
From Java 9 it is possible to create JAR files for Java modules which contains code compiled specifically for different versions of Java. That means, that you can create a JAR file for your module that contains code compiled for Java 8, Java 9, Java 10 etc. — all within the same JAR file.
Here is how the structure looks of a multi Java version JAR file:
- META-INF
- MANIFEST.MF
- versions
- 10
- com
- com
The com folder at the root level of the JAR file contains the compiled Java classes for pre Java 9 versions. Earlier versions of Java do not understand multi Java version JAR files so they use the classes found here. Therefore you can only support one Java version earlier than Java 9.
The META-INF directory contains the MANIFEST.MF file and a directory named versions . The MANIFEST.MF file needs a special entry that marks the JAR file as a multi version JAR file. Here is how this entry looks:
The versions directory which can contain the compiled classes for different versions of Java for your module. In the example above there are two subdirectories inside the versions directory. One subdirectory for Java 9, and one for Java 10. The names of the directories correspond to the numbers of the Java versions to support.
Migrating to Java 9
Parts of the Java Platform Module System are designed to ease migration of applications written in pre Java 9 to Java 9. The module system is designed to support both «bottom up» and «top down» migration. Bottom up migration means that you migrate your small utility libraries first, and your main applications last. Top down migration means that you migrate your application first, and the utility libraries later on.
The migration can go something like this:
-
Upgrade to Java 9, and run your application without modularizing anything.
Put classes and JAR files on the classpath as normal. These classes then become part of the unnamed module.By following this migration process the chance is higher of your applications being able to work in the period before everything is upgraded to Java modules. Your application is supposed to be able to work during any of the above phases.
Upgrading utility libraries first to automatic modules, and later to full modules, starting at the bottom of the dependency hierarchy should assure that your libraries can still read each other during upgrade, plus be readable by the main applications on the classpath in the unnamed module or as an automatic or named module.
Note: If there is any chance of you upgrading everything in one big upgrade, that will most likely be the easiest upgrade path. Of course you will have some time where you may not be able to assemble a working application, but once you are through it, you are through it! You are not dragging old dependencies around in your application. If possible, this is definitely preferable.
Модульность в Java 9
Основным нововведением Java 9 было именно введение модульности. Про эту фичу было много разговоров, дата релиза несколько раз переносилась, чтобы допилить все должным образом. В этом посте речь пойдет о том, что дает механизм модулей, и чего полезного Java 9 принесла в целом. Основой для поста послужил доклад моего коллеги — Сергея Малькевича.
Для реализации модулей в этой версии Java был выделен целый проект — Project Jigsaw — который включает в себя несколько JEP и JSR.

Для любителей официальной документации, ознакомиться более подробно с каждым JEP можно здесь.
Подробнее о Project Jigsaw
Project Jigsaw, который реализует модульность, начал разрабатываться в далеком 2005: сначала вышел JSR 277, а уже в 2008 началась непосредственная работа над проектом. Релиз состоялся только в 2017 году. То есть, для того, чтобы докрутить модули в Java, понадобилось почти 10 лет. Что, собственно, подчеркивает весь масштаб работы и изменений, которые были внесены в ходе реализации модульности.
Какие цели ставили перед собой разработчики:
- облегчить разработку больших приложений и библиотек;
- улучшить безопасность Java SE в целом, и JDK в частности;
- увеличить производительность приложений;
- создать возможность уменьшения размера JRE для запуска на небольших девайсах, чтобы не потреблять слишком много памяти;
- JAR HELL (об этом чуть позже).
Чего полезного принесла Java 9
До 9 версии, JDK и JRE были монолитными. Их размер рос с каждым релизом. Java 8 занимала уже сотни мегабайт, и все это разработчикам приходилось “таскать с собой” каждый раз, чтобы иметь возможность запускать Java приложения. Один только rt.jar весит порядка 60 Mb. Ну и сюда еще добавляем медленный старт и высокое потребление памяти. Тут на помощь пришла Java 9.
В JDK 9 было введено разделение на модули, а именно, JDK была разделена на 73 модуля. И с каждой новой версией количество этих модулей растет. В 11 версии это число близится к 100. Это разделение позволило разработчикам создать утилиту JLINK. С помощью JLINK можно создавать кастомные наборы JRE, которые будут включают только «нужные» модули, которые реально необходимы вашему приложению. Таким образом, простое приложение и какой-либо customJRE с минимальным (или небольшим) набором модулей в итоге может уместиться в 20 Mb, что не может не радовать.
Список модулей можно посмотреть здесь.
С приходом Java 9 поменялась структура JDK: теперь она идентична структуре JRE. Если раньше JDK включала папку JRE, где снова имеется bin и дублируются файлы, то теперь все выглядит следующим образом:

Модули
Собственно. что такое модуль? Модуль — это новый уровень агрегации пакетов и ресурсов (ориг. “a uniquely named, reusable group of related packages, as well as resources and a module descriptor”).
Модули поставляются в JAR файлах с пакетами и дескриптором модуля
module-info.java. Файл module-info.java содержит описание модуля:
имя, зависимости, экспортируемые пакеты, потребляемые и предоставляемые сервисы, разрешения для reflection доступа.Примеры описания дескриптора модуля:
После ключевого слова module у нас идет имя пакета jdk.javadoc, который зависит от другого пакета java.xml и транзитивно зависит от других пакетов.
Давайте подробнее пройдемся по каждому из ключевых слов:
- requires указывает модули, от которых зависит текущий модуль;
- requires transitive — транзитивная зависимость — означает следующее: если модуль m1 транзитивно зависит от модуля m2, и мы имеем какой-то третий модуль mX, который зависит от m1 — модуль mX будет иметь доступ также и к m2;
- requires static позволяет указать compile-time зависимости;
- exports указывает пакеты, которые экспортирует текущий модуль (не включая “подпакеты”);
- exports. to… позволяет ограничить доступ: export com.my.package.name to com.specific.package; то есть можно открыть доступ к пакету нашего модуля только для какого-то другого(их) пакета(ов) другого модуля;
Допустим, у нас подключено несколько модулей, которые реализуют абстрактный сервис — MyService. При сборке приложения у нас есть возможность решить, какую реализацию сервиса использовать, “перетащив” нужные нам модули реализации сервиса на —module-path:
Таким образом, возвращенный Iterator содержит список реализаций интерфейса MyService. Фактически, он будет содержать все реализации, найденные в модулях, найденных на —module-path.
Зачем в принципе были введены сервисы? Они нужны для того, чтобы показать, как наш код будет использован. То есть, здесь заключена семантическая роль. Также, модульность — это про инкапсуляцию и безопасность, так как мы можем сделать реализацию private и исключить возможность несанкционированного доступа через reflection.
Также, один из вариантов использования сервисов — это достаточно простая реализация плагинов. Мы можем реализовать интерфейс плагина для нашего приложения и подключать модули для работы с ними.
Вернемся к синтаксису описания модулей:
До 9ки через reflection мы имели доступ практически ко всему и могли делать все, что хотим и с чем хотим. А 9-ая версия, как уже упоминалось, позволяет обезопасить себя от “нелегального” reflection доступа.
Мы можем полностью открыть модуль для reflection доступа, объявив open:
Либо, мы можем указать какие либо пакеты для reflection доступа, объявив opens:
Здесь же есть возможность использовать opens com.my.coolpackage to…, таким образом предоставляя reflection доступ пакету com.my.coolpackage из пакета, который укажем после to.
Типы модулей
Project Jigsaw классифицирует модули следующим образом:
- System Modules — Java SE и JDK модули. Полный список можно посмотреть, используя команду java —list-modules.
- Application Modules — модули нашего приложения, которые мы написали, а также те зависимости (от сторонних библиотек), которые использует наше приложение.
- Automatic Modules — это модули с открытым доступом, создаваемые Java автоматически из JAR-файлов. Допустим, мы хотим запустить наше приложение в модульном режиме, но оно использует какую-то библиотеку. В этом случае мы помещаем JAR-файл на —module-path и Java автоматически создает модуль с именем, унаследованным от имени JAR-файла.
- Unnamed Module — безымянный модуль, автоматически создаваемый из всех JAR-файлов, которые загружены на —class-path. Это универсальный модуль для обеспечения обратной совместимости с ранее написанным Java кодом.
Class-path vs module-path
С появлением модулей появилось новое понятие — module-path. По сути, это тот же class-path, но для модулей.
Запуск модульного приложения выглядит следующим образом:

В обычном режиме запуска мы указываем опции и полный путь к мейн классу. В случае, если мы хотим работать с модулями, мы также указываем опции и параметр -m либо -module, который как раз указывает на то, что мы будем запускать модули. То есть, мы автоматически переводим наше приложение в модульный режим. Далее мы указываем имя модуля и путь к мейн классу из модуля.
Также, если в обычном режиме мы привыкли работать с параметром -cp и —class-path, в режиме модульности мы прописываем новый параметр -p и —module-path, который указывает пути к используемым в приложении модулям.
Часто встречаюсь с тем, что разработчики не переходят на версии 9+, так как считают, что им придется работать с модулями. Хотя на самом деле, мы можем запускать наши приложения в старом режиме, попросту не прописывая параметр и не используя модули, а используя только другие новые фишки.
JAR HELL
Хочу также по диагонали остановится на проблеме Jar Hell.

Что такое Jar Hell в двух словах? Например, у нас есть какое-то наше приложение и оно зависит от библиотеки X и библиотеки Y. При этом, обе эти библиотеки зависят от библиотеки Z, но от разных версий: X зависит от версии 1, Y — от версии 2. Хорошо, если версия 2 обратно совместима с версией 1, тогда никаких проблем не возникнет. А если нет — очевидно, что мы получаем конфликт версий, то есть одна и та же библиотека не может быть загружена в память одним и тем же загрузчиком классов.
Как в этом случае выходят из ситуации? Есть стандартные методы, которые разработчики используют со времен самой первой Java, например, exclude, кто-то использует плагины для Maven, которые переименовывают названия корневых пакетов библиотеки. Либо же, разработчики ищут разные версии библиотеки X, чтобы подобрать совместимый вариант.
К чему это я: первые прототипы Jigsaw подразумевали наличие версии у модуля и позволяли загрузку нескольких версий через разные ClassLoader’ы, но позже от это отказались. В итоге, “серебряной пули”, которую многие ждали, не вышло.
Но, “прямо-из-коробки” нас немного обезопасили от подобных проблем. В Java 9 запрещены Split Packages — пакеты, которые разделены на несколько модулей. То есть, если у нас есть пакет com.my.coolpackage в одном модуле, мы не можем его использовать в другом модуле в рамках одного приложения. При запуске приложения с модулями, содержащими одинаковые пакеты, мы просто упадем. Это небольшое улучшение исключает возможность непредсказуемого поведения в связи с загрузкой Split пакетов.
Также, помимо самих модулей, есть еще механизм слоев или Jigsaw Layers, который также помогает справится с проблемой Jar Hell.
Jigsaw слой можно определить как некоторую локальную модульную систему. И здесь стоит отметить, что Split пакеты, о которых шла речь выше, запрещены только в рамках одного Jigsaw слоя. Модули с одинаковыми пакетами имеют место быть, но они должны принадлежать разным слоям.
Выглядит это следующим образом:

При старте приложения создается слой boot, куда входят модули платформы, загружаемые Bootstrap, добавочные модули платформы, загружаемые платформенным загрузчиком и модули нашего приложения, загружаемые Application загрузчиком.
В любой момент, мы можем создать свои слои и “положить” туда модули разных версий и при этом не упасть.
Есть отличный подробный доклад на YouTube на эту тему: Спасение от Jar Hell с помощью Jigsaw Layers
Заключение
Механизм модулей из Java 9 открывает нам новые возможности, при этом поддержка библиотек на сегодняшний день довольно небольшая. Да, люди запускают Spring, Spring Boot и так далее. Но большинство библиотек так и не перешло на полное использование модулей. Видимо поэтому, все эти изменения были восприняты довольно скептически техничесим сообществом. Модули предоставляют нам новые возможности, но вопрос востребованности остаётся открытым.
- 10
- jenkov
- com
- jenkov