Viewing Dependencies – Java Module System

Viewing Dependencies

The Java Dependency Analysis tool, jdeps, is a versatile command-line tool for static analysis of dependencies between Java artifacts like class files and JARs. It can analyze dependencies at all levels: module, package, and class. It allows the results to be filtered and aggregated in various ways, even generating various graphs to illustrate its findings. It is an indispensable tool when migrating non-modular code to make use of the module system.

In this section, we confine our discussion to modular code—that is, either an exploded module directory with the compiled module code (e.g., mods/main) or a modular JAR (e.g., mlib/main.jar).

In this section, the results from some of the jdeps commands have been edited to fit the width of the page without any loss of information, or elided to shorten repetitious outputs.

Viewing Package-Level Dependencies

The jdeps command with no options, shown below, illustrates the default behavior of the tool. Line numbers have been added for illustrative purposes.

When presented with the root module directory mods/model (1), the default behavior of jdeps is to print the name of the module (2), the path to its location (3), its module descriptor (4), followed by its module dependencies (5), and lastly the package-level dependencies (6). The package-level dependency at (6) shows that the package com.passion.model in the model module depends on the java.lang package in the proverbial java.base module.

Click here to view code image

(1) 
>jdeps mods/model

(2)  model
(3)  [file:…/adviceApp/mods/model/]
(4)     requires mandated java.base (@17.0.2)
(5)  model -> java.base
(6)     com.passion.model           -> java.lang           java.base

The following jdeps command if let loose on the model.jar archive will print the same information for the model module, barring the difference in the file location:

>
jdeps mlib/model.jar

However, the following jdeps command with the main.jar archive gives an error:

Click here to view code image

>jdeps mlib/main.jar

Exception in thread “main” java.lang.module.FindException: Module controller
not found, required by main
       at java.base/…

Dependency analysis cannot be performed on the main module by the jdeps tool because the modules the main module depends on cannot be found. In the case of the model module, which does not depend on any other user-defined module, the dependency analysis can readily be performed.

The two options –module-path (no short form) and –module (short form: -m) in the jdeps command below unambiguously specify the location of other modular JARs and the module to analyze, respectively. The format of the output is the same as before, and can be verified easily.

Click here to view code image

>jdeps –module-path mlib –module main
main
 [file:…/adviceApp/mlib/main.jar]
   requires controller
   requires mandated java.base (@17.0.2)
main -> controller
main -> java.base
   com.passion.main     -> com.passion.controller     controller
   com.passion.main     -> java.lang                  java.base

However, if one wishes to analyze all modules that the specified module depends on recursively, the –recursive option (short form: -R) can be specified. The output will be in the same format as before, showing the package-level dependencies for each module. The output from the jdeps command for each module is elided below, but has the same format we have seen previously.

Click here to view code image

>
jdeps –module-path mlib –module main –recursive

controller

main

model

view


Absolute and Relative Paths – Java I/O: Part II

Absolute and Relative Paths

Directory entries can be referenced using both absolute and relative paths, but the path naming must follow the conventions of the host platform.

An absolute path starts with the platform-dependent root component of the file system, as in the examples above. All information is contained in an absolute path to reference the directory entry—that is, an absolute path uniquely identifies a directory entry in the file system.

A relative path is without designation of the root component and therefore requires additional path information to locate its directory entry in the file system. Typically, a relative path is interpreted in relation to the current directory (see the next subsection). Some examples of relative paths are given below, but they alone are not enough to uniquely identify a directory entry.

Click here to view code image

c/d                on Unix-based platforms
c\d                on Windows-based platforms

Note that in a path, the name elements are all parent directory names, except for the last name element, which can be either a file name or a directory name. The name of a directory entry does not distinguish whether it is a file or a directory in the file system. Although file extensions can be used in a file name for readability, it is immaterial for the file system. Care must also be exercised when choosing name elements, as characters allowed are usually platform dependent.

Java programs should not rely on system-specific path conventions. In the next section we will construct paths in a platform-independent way.

Current and Parent Directory Designators

A file system has a notion of a current directory which changes while traversing in the file system. The current directory is designated by the period character (.) and its parent directory by two periods (..). These designators can be used in constructing paths. Given that the current directory is /a/b in the directory tree shown earlier, Table 21.1 illustrates relative paths constructed using the current and the parent directory designators, and their corresponding absolute paths.

Table 21.1 Using Current and Parent Directory Designators

Relative path (Current directory: /a/b)Absolute path
./c/d/a/b/c/d
./a/b
./../a
./../../ (i.e., the root component)

Symbolic Links – Java I/O: Part II

Symbolic Links

Apart from regular files, a file system may allow symbolic links to be created. A symbolic link (also called a hard link, a shortcut, or an alias) is a special file that acts as a reference to a directory entry, called the target of the symbolic link. Creating an alias of a directory entry in the file system is similar to creating a symbolic link. Using symbolic links is transparent to the user, as the file system takes care of using the target when a symbolic link is used in a file operation, with one caveat: Deleting the symbolic link does not delete the target. In many file operations, it is possible to indicate whether symbolic links should be followed or not.

21.2 Creating Path Objects

File operations that a Java program invokes are performed on a file system. Unless the program utilizes a specific file system constructed by the factory methods of the java.nio.file.FileSystems utility class, file operations are performed on a file system called the default file system that is accessible to the JVM. We can think of the default file system as the local file system. The default file system is platform specific and can be obtained as an instance of the java.nio.file.FileSystem abstract class by invoking the getDefault() factory method of the FileSystems utility class.

Click here to view code image

FileSystem dfs = FileSystems.getDefault();     // The default file system

File operations can access the default file system directly to perform their operations. The default file system can be queried for various file system properties.

Click here to view code image

static FileSystem getDefault()        
Declared in
 java.nio.file.FileSystems

Returns the platform-specific default file system. If the method is called several times, it returns the same FileSystem instance for the default file system.

Click here to view code image

abstract String getSeparator()        
Declared in
 java.nio.file.FileSystem

Returns the platform-specific name separator for name elements in a path, represented as a string.

Paths in the file system are programmatically represented by objects that implement the Path interface. The Path interface and various other classes provide factory methods that create Path objects (see below). A Path object is also platform specific.

A Path object implements the Iterable<Path> interface, meaning it is possible to traverse over its name elements from the first name element to the last. It is also immutable and therefore thread-safe.

In this section, we look at how to create Path objects. A Path object can be queried and manipulated in various ways, and may not represent an existing directory entry in the file system (p. 1294). A Path object can be used in file operations to access and manipulate the directory entry it denotes in the file system (p. 1304).

The methods below can be used to create Path objects. The methods are also shown in Figure 21.1, together with the relationship between the different classes of these methods. Note in particular the interoperability between the java.nio.file.Path interface that represents a path and the two classes java.io.File and java.net.URI. The class java.io.File represents a pathname in the standard I/O API and the class java.net.URI represents a Uniform Resource Identifier (URI) that identifies a resource (e.g., a file or a website).

Click here to view code image

static Path of(String first, String… more)
static Path of(URI uri)

These methods are declared in the java.nio.file.Path interface.

Click here to view code image

abstract Path getPath(String first, String… more)

This method is declared in the java.nio.file.FileSystem class.

Click here to view code image

static Path get(String first, String… more)
static Path get(URI uri)

These static methods are declared in the java.nio.file.Paths class.

Path toPath()

This method is declared in the java.io.File class.

Figure 21.1 Creating Path Objects

Creating Path Objects with the Path.of() Method – Java I/O: Part II

Creating Path Objects with the Path.of() Method

The simplest way to create a Path is to use the static factory method Path.of(String first, String… more). It joins the first string with any strings in the variable arity parameter more to create a path string that is the basis of constructing a Path object. This method creates a Path object in accordance with the default file system.

For instance, the default file system can be queried for the platform-specific name separator for name elements in a path.

The three absolute paths below are equivalent on a Unix platform, as they create a Path object based on the same path string. The nameSeparator string is the platform-specific name separator obtained from the default file system. At (1) and (2) below, only the first string parameter is specified in the Path.of() method, and it is the basis for constructing a Path object. However, specifying the variable arity parameter where possible is recommended when a Path object is constructed as shown at (3), as joining of the name elements is implicitly done using the platform-specific name separator. The equals() methods simply checks for equality on the path string, not on any directory entry denoted by the Path objects.

Click here to view code image

FileSystem dfs = FileSystems.getDefault();     // Obtain the default file system.
String nameSeparator = dfs.getSeparator();     // The name separator for a path.

Path absPath1 = Path.of(“/a/b/c”);                         // (1) /a/b/c
Path absPath2 = Path.of(nameSeparator + “a” +              // (2) /a/b/c
                        nameSeparator + “b” +
                        nameSeparator + “c”);
Path absPath3 = Path.of(nameSeparator, “a”, “b”, “c”);     // (3) /a/b/c
System.out.println(absPath1.equals(absPath2) &&
                   absPath2.equals(absPath3));             // true

The two absolute paths below are equivalent on a Windows platform, as they create Path objects based on the same path string. Note that the backslash character must be escaped with a second backslash in a string. Otherwise, it will be interpreted as starting an escape sequence (§2.1, p. 38).

Click here to view code image

Path absPath4 = Path.of(“C:\\a\\b\\c”);                    // (4) C:\a\b\c
Path absPath5 = Path.of(“C:”, “a”, “b”, “c”);              // (5) C:\a\b\c

The Path created below is a relative path, as no root component is specified in the arguments.

Click here to view code image

Path relPath1 = Path.of(“c”, “d”);                         //  c/d

Often we need to create a Path object to denote the current directory. This can be done via a system property named “user.dir” that can be looked up, as shown at (1) below, and its value used to construct a Path object, as shown at (2). The path string of the current directory can be used to create paths relative to the current directory, as shown at (3).

Click here to view code image

String pathOfCurrDir = System.getProperty(“user.dir”);  // (1)
Path currDir = Path.of(pathOfCurrDir);                  // (2)
Path relPath = Path.of(pathOfCurrDir, “d”);             // (3) <curr-dir-path>/d

Viewing Module Dependencies – Java Module System

Viewing Module Dependencies

If the default output from the jdeps command is overwhelming, it can be filtered. The -summary option (short form: -s) will only print the module dependencies, as shown by the jdeps command below. Only the module dependencies of the main module will be shown in the output.

Click here to view code image

>
jdeps –module-path mlib –module main -summary

main -> controller
main -> java.base

If we use the –recursive option (short form: -R) with the -summary option (short form: -s), then the module dependencies of each module will be printed recursively, starting with the specified module.

Click here to view code image

>
jdeps –module-path mlib –module main -summary –recursive              (1)

controller -> java.base
controller -> model
controller -> view
main -> controller
main -> java.base
model -> java.base
view -> java.base
view -> model

Finally, we illustrate the graph-generating capabilities of the jdeps tool. The jdeps command takes all JARs of the adviceApp application from the mlib directory and creates a module graph (options -summary and –recursive) in the DOT format (option -dotoutput) under the current directory. The DOT file summary.dot containing the graph will be created. Using the dot command, this graph can be converted to a pdf file (summary.pdf) as shown in Figure 19.18.

Figure 19.18 Module Graph Using the jdeps Tool

Click here to view code image

>
jdeps -dotoutput . -summary –recursive  mlib/*
>
dot -Tpdf summary.dot >summary.pdf

The module graph in Figure 19.18 shows the same module dependencies printed by the jdeps command above at (1). Comparing the module graph in Figure 19.18 with the one in Figure 19.8, we see that jdeps has added the implicit dependency of each module on the java.base module.

Viewing Class-Level Dependencies

It is possible to dive deeper into dependencies with the jdeps tool. The -verbose option (short form: -v) will elicit the class dependencies of the specified module. Instead of package dependencies to round off the output, class dependencies are listed. The last line in the output shows that the class com.passion.main.Main in the main module depends on the java.lang.String class in the java.base module.

Click here to view code image

>
jdeps –module-path mlib –module main -verbose
main
 [file:…/adviceApp/mlib/main.jar]
   requires controller
   requires mandated java.base (@17.0.2)
main -> controller
main -> java.base
   com.passion.main.Main  -> com.passion.controller.AdviceController  controller
   com.passion.main.Main  -> java.lang.Object                         java.base
   com.passion.main.Main  -> java.lang.String                         java.base

If we use the –recursive option (short form: -R) with the -verbose option (short form: -v), then the class dependencies of each module will be printed recursively, starting with the specified module.

Click here to view code image

>
jdeps –module-path mlib –module main -verbose –recursive

controller

main

model

view


Creating Path Objects with the Paths.get() Method – Java I/O: Part II

Creating Path Objects with the Paths.get() Method

The Paths utility class provides the get(String first, String… more) static factory method to construct Path objects. In fact, this method invokes the Path.of(String first, String… more) convenience method to obtain a Path.

Click here to view code image

Path absPath7 = Paths.get(nameSeparator, “a”, “b”, “c”);
Path relPath3 = Paths.get(“c”, “d”);

Creating Path Objects Using the Default File System

We have seen how to obtain the default file system that is accessible to the JVM:

Click here to view code image

FileSystem dfs = FileSystems.getDefault();     // The default file system

The default file system provides the getPath(String first, String… more) method to construct Path objects. In fact, the Path.of(String first, String… more) method is a convenience method that invokes the FileSystem.getPath() method to obtain a Path object.

Click here to view code image

Path absPath6 = dfs.getPath(nameSeparator, “a”, “b”, “c”);
Path relPath2 = dfs.getPath(“c”, “d”);

Interoperability with the java.io.File Legacy Class

An object of the legacy class java.io.File can be used to query the file system for information about a file or a directory. The class also provides methods to create, rename, and delete directory entries in the file system. Although there is an overlap of functionality between the Path interface and the File class, the Path interface is recommended over the File class for new code. The interoperability between a File object and a Path object allows the limitations of the legacy class java.io.File to be addressed.

Click here to view code image

File(String pathname)
Path toPath()

This constructor and this method of the java.io.File class can be used to create a File object from a pathname and to convert a File object to a Path object, respectively.

default File toFile()

This method of the java.nio.file.Path interface can be used to convert a Path object to a File object.

The code below illustrates the round trip between a File object and a Path object:

Click here to view code image

File file = new File(File.separator + “a” +
                     File.separator + “b” +
                     File.separator + “c”);        // /a/b/c
// File –> Path, using the java.io.File.toPath() instance method
Path fileToPath = file.toPath();                   // /a/b/c
// Path –> File, using the java.nio.file.Path.toFile() default method.
File pathToFile = fileToPath.toFile();             // /a/b/c

Summary of Selected Operations with the JDK Tools – Java Module System

19.14 Summary of Selected Operations with the JDK Tools

Table 19.6 provides a ready reference for commands to perform miscellaneous operations using the JDK tools introduced in this chapter. Short forms for the options are shown where appropriate. Use the indicated reference for each operation to learn more about it. The only way to become familiar with these tools is to use them.

Table 19.6 Selected Operations with the JDK Tools

OperationCommand
Compiling modular code (p. 1186)Click here to view code image javac –module-path
modulepath
 -d
directory sourceAndModuleInfoFiles

javac  -p          
modulepath
 -d
directory sourceAndModuleInfoFiles

javac –module-source-path
modulepath
 -d
directory \

      –module
moduleName1
,…
javac –module-source-path
modulepath
 -d
directory
 \
       -m
moduleName1
,…

Launching modular application (p. 1189)Click here to view code image java –module-path
modulepath
 –module
moduleName/qualifiedClassName

java  -p          
modulepath
  -m     
moduleName/qualifiedClassName

java –module-path
modulepath
  -module
moduleName

java  -p          
modulepath
  -m     
moduleName

Creating and listing a modular JAR archive (p. 1189)Click here to view code image jar –verbose –create –file
jarfile -C directory files

jar -vcf
jarfile
 -C
directory
 .
jar -vcf
jarfile
 –main-class
qualifiedMainClassName
 -C
directory
 .
jar -vcfe
jarfile qualifiedMainClassName
 -C
directory
 .

jar –list –file
jarfile

jar -tf
jarfile

Listing available modules (p. 1212)Click here to view code image java –list-modules
java –module-path
modulepath
 –list-modules
java  -p          
modulepath
 –list-modules

Describing a module—that is, printing the module descriptor (p. 1213)Click here to view code image java –module-path
modulepath
 –describe-module
moduleName

java  -p          
modulepath
  -d              
moduleName


jar –file
jarFile
 –describe-module
jar  -f   
jarFile
  -d

Viewing package-level dependencies (p. 1214)Click here to view code image jdeps –module-path
modulepath
 -m
moduleName

jdeps –module-path
modulepath
 -m
moduleName
 –recursive
jdeps –module-path
modulepath
 -m
moduleName
 -R

Viewing module dependencies (p. 1216)Click here to view code image jdeps –module-path
modulepath
 -m
moduleName
 -summary
jdeps –module-path
modulepath
 -m
moduleName
 -summary –recursive
jdeps –module-path
modulepath
 -m
moduleName
 -s -R

Viewing class-level dependencies (p. 1216)Click here to view code image jdeps –module-path
modulepath
 -m
moduleName
 -verbose
jdeps –module-path
modulepath
 -m
moduleName
 -verbose –recursive
jdeps –module-path
modulepath
 -m
moduleName
 -v -R

Selected Options for the javac Tool – Java Module System

Selected Options for the javac Tool

The Java language compiler, javac, compiles Java source code into Java bytecode. The general form of the javac command is:

javac
[options] [sourcefiles]

Table 19.7 shows some selected options that can be used when compiling modules. The optional sourcefiles is an itemized list of source files, often omitted in favor of using module-related options.

Table 19.7 Selected Options for the javac Tool

OptionDescription
–module-source-path
   
moduleSourcePath

The moduleSourcePath specifies the source directory where the exploded modules with the source code files can be found.
–module-path
modulepath

 -p          
modulepath

The modulepath specifies where the modules needed by the application can be found. This can be a root directory of the exploded modules with the class files or a root directory where the modular JARs can be found. Multiple directories of modules can be specified, separated by a colon (:) on a Unix-based platform and semicolon (;) on the Windows platform.
–module
moduleName

 -m     
moduleName

Specifies the module(s) to be compiled. Can be a single module name or a comma-separated (,) list of module names. For each module specified, all modules it depends on are also compiled, according to the module dependencies.
-d
classesDirectory

Specifies the destination directory for the class files. Mandatory when compiling modules. For classes in a package, their class files are put in a directory hierarchy that reflects the package name, with directories being created as needed. Without the -d option, the class files are put in the same directory as their respective source files. Specifying the directory path is platform dependent: slash (/) on Unix-based platforms and backslash (\) on Windows platforms being used when specifying the directory path.
–add-modules
module,…

Specifies root modules to resolve in addition to the initial modules. These modules can be modular JAR files, JMOD files, or even exploded modules.

Selected Options for the java Tool

The java tool launches an application—that is, it creates an instance of the JVM in which to run the application. A typical command to launch a modular application is by specifying the location of its modules (path) and the entry point of the application (as module or module/mainclass):

Click here to view code image

java –module-path
path
 –module
module[/mainclass]

Table 19.8 shows some selected options that can be used for launching and exploring modular applications.

Table 19.8 Selected Options for the java Tool

OptionDescription
–module-path
modulepath…

 -p          
modulepath

The modulepath specifies the location where the modules needed to run the application can be found. This can be a root directory for the exploded modules with the class files or a directory where the modular JARs can be found. Multiple directories of modules can be specified, separated by a colon (:) on a Unix-based platform and semicolon (;) on Windows platforms.
–module
module
[/
mainclass
]
 -m     
module
[/
mainclass
]

When module/mainclass is specified, it explicitly states the module name and the fully qualified name of the class with the main() method, thereby overriding any other entry point in the application. Without the mainclass, the entry point of the application is given by the module which must necessarily contain the main-class.
–add-modules
module,…

Specifies root modules to resolve in addition to the initial modules.
–list-modules

Only lists the observable modules, and does not launch the application. That is, it lists the modules that the JVM can use when the application is run.
–describe-module
moduleName

 -d              
moduleName

Describes a specified module, in particular its module descriptor, but does not launch the application.
–validate-modules

Validates all modules on the module path to find conflicts and errors within modules.

Selected Options for the jar Tool – Java Module System

Selected Options for the jar Tool

The jar tool is an archiving and compression tool that can be used to bundle Java artifacts and any other resources that comprise the application. The archive file names have the .jar extension. A typical command to create a modular JAR (jarfile) with an application entry point (qualifiedMainClassName), based on the contents of a specific directory (DIR), is shown below. Note the obligatory dot (.) at the end of the command.

Click here to view code image

jar –create –file
jarfile
 –main-class
qualifiedMainClassName
 -C
DIR
 .

Table 19.9 gives an overview of some selected options that can be used for working with JARs.

Table 19.9 Selected Options for the jar Tool

OptionDescription
–create or -cCreates a new archive.
–extract or -xExtracts specified or all files in the archive.
–list or -tLists the contents of the archive.
–update or -uUpdates an existing archive with specified files.
–describe-module or -dPrints the module descriptor of the archive and the main-class, if one is specified in the manifest.
–verbose or -vPrints extra information about the operation.
–file
jarfile

–file=
jarfile

 -f
jarfile

 -f=
jarfile

Specifies the name of the archive.
-C DIR filesChanges to the specified directory and includes the contents of the specified files from this directory. If files is a dot (.), the contents under the specified directory DIR are included.
Click here to view code image –main-class
qualifiedMainClassName

–main-class=
qualifiedMainClassName

 -e
qualifiedMainClassName

 -e=
qualifiedMainClassName

Specifies the entry point of the application.
–manifest
TXTFILE

–manifest=
TXTFILE

 -m
TXTFILE

 -m=
TXTFILE

Reads the manifest information for the archive from the specified TXTFILE and incorporates it in the archive—for example, the value of the Main-Class attribute that specifies the entry point of the application.
–module-path
modulepath

 -p          
modulepath

Specifies the location of the modules for recording hashes.

Selected Options for the jdeps Tool

The Java Class Dependency Analyzer, jdeps, is the tool of choice when working with modules, as it is module savvy and highly versatile. Among its extensive module analyzing capabilities, it can be used to explore dependencies at different levels: module level, package level, and class level.

Table 19.10 gives an overview of some selected options for the jdeps tool that can be used for analyzing modules.

Table 19.10 Selected Options for the jdeps Tool

OptionDescription
–module-path modulepathSpecifies where to find the module JARs needed by the application. No short form, as -p is already reserved for –package.
–module-name
moduleName

 -m          
moduleName

Specifies the root module for module dependency analysis.
-summary or -sPresents only a summary of the module dependencies.
–recursive or -RForces jdeps to recursively iterate over the module dependencies. When used alone, also prints the package-level dependencies.
-verbose or -vAlso includes all class-level dependencies in the printout.
-verbose:packageIncludes package-level dependencies in the printout, excluding, by default, dependencies within the same package.
-verbose:classIncludes class-level dependencies in the printout, excluding, by default, dependencies within the same JAR.

Selected Options for the jlink Tool – Java Module System

Selected Options for the jlink Tool

The jlink tool creates a runtime image of an application. A typical command to create a runtime image of an application requires the location of its modules (path), names of modules to include (module_names), and output directory to store the runtime image (output_dir):

Click here to view code image

jlink –module-path
path
 –add-modules
module_names
 –output
output_dir

The runtime image can be executed by the output_dir/java command.

Selected options for the jlink tool are summarized in Table 19.11.

Table 19.11 Selected Options for the jlink Tool

OptionDescription
–module-path
modulepath…

 -p          
modulepath

Specifies the location where the modules for the application can be found. This can be a root directory for the exploded modules with the class files or a directory where the modular JARs can be found. Multiple directories of modules can be specified, separated by a colon (:) on Unix-based platforms and semicolon (;) on Windows platforms.
–add-modules module,…Specifies the modules to include in the generated runtime image. All application modules must be listed. Any standard or JDK modules needed will be automatically included.
–output pathSpecifies the location of the generated runtime image.

Final Remarks on Options for the JDK Tools

It is worth taking a note of how the command options having the short form -p, -m, and -d are specified in different JDK tools. Table 19.12 gives an overview of which long form they represent in which tool and how they are specified.

Table 19.12 Selected Common Shorthand Options for JDK Tools

javacjavajarjdeps
–module-path
path

 -p           
path

–module-path
path

 -p          
path

–module-path
path

 -p          
path

–module-path path (no short form as -p is reserved for –package)
–module
module

 -m     
module

–module
module
[/
mainclass
]
 -m     
module
[/
mainclass
]

–manifest
TXTFILE

 -m       
TXTFILE

–module
module

 -m
module

(no long form)
-d classesDirectory
–describe-module
module

 -d              
module

–describe-module
 -d


(no -d option)