Current Tutorial
Implied Readability with requires transitive
Next in the Series

Previous in the Series: Optional Dependencies with requires static

Next in the Series: Qualified exports and opens

Implied Readability with requires transitive

The module system has strict rules for accessing code in other modules and one of them is that the accessing module must read the accessed one. The most common way to establish readability is for one module to require another, but it's not the only one. If a module uses types from another module in its own API, every outsider using the first module would be forced to also require the second. Unless the first module uses requires transitive for the second one, which implies readability of the second module for any module that reads the first. That's a bit confusing, but you'll get it in a few minutes.

Note: You need to know the module system basics to get the most out of this article.

Implied Readability

In the common case, a module uses a dependency internally without the outside world having any knowledge of it. Take, for example, java.prefs, which requires java.xml: It needs the XML parsing capabilities, but its own API neither accepts nor returns types from java.xml's packages.

But there is another use case where the dependency is not entirely internal, but lives on the boundary between modules. In that scenario, one module depends on another, and exposes types from the depended-upon module in its own public API. A good example is java.sql. It also uses java.xml but unlike java.prefs not just internally - the public class java.sql.SQLXML maps the SQL XML type and as such uses types from java.xml its own API. Similarly, java.sql's Driver has a method getParentLogger() that returns a Logger, which is a type from the java.logging module.

In such situations, code that wants to call the module (e.g. java.sql) might have to use types from the depended-upon module (e.g. java.xml, java.logging). But it can't do that if it does not also read the depended-upon module. Hence for the module to be at all usable, clients would all have to explicitly depend on that second module as well. Identifying and manually resolving such hidden dependencies would be a tedious and error-prone task.

This is where implied readability comes in. It extends module declarations so that one module can grant readability of modules upon which it depends to any module that depends upon it. Such implied readability is expressed by including the transitive modifier in a requires clause.

That's why java.sql's module declaration looks as follows:

module java.sql {
    requires transitive java.logging;
    requires transitive java.transaction.xa;
    requires transitive java.xml;

    exports java.sql;
    exports javax.sql;

    uses java.sql.Driver;
}

That means any module that reads java.sql (usually by requiring it) will automatically also read java.logging, java.transaction.xa, and java.xml.

When to Rely on Implied Readability

Original explainers of the module system include a clear recommendation when to use implied readability:

In general, if one module exports a package containing a type whose signature refers to a package in a second module then the declaration of the first module should include a requires transitive dependence upon the second. This will ensure that other modules that depend upon the first module will automatically be able to read the second module and, hence, access all the types in that module's exported packages.

But how far should you take this? Looking back on the example of java.sql, should a module using it require java.logging as well? Technically such a declaration is not needed and might seem redundant.

To answer this question we have to look at how exactly the fictitious module uses java.logging. It might only need to read it so you are able to call Driver.getParentLogger(), for example to change the logger's log level, and nothing more. In this case your code's interaction with java.logging happens in the immediate vicinity of its interaction with Driver from java.sql. Above we called this the boundary between two modules.

Alternatively your module might actually use logging throughout its own code. Then, types from java.logging appear in many places independent of Driver and can no longer be considered to be limited to the boundary of your module and java.sql.

It is recommended to only rely on implied readability of a module (e.g. java.logging) if its types are only used on the boundary to the module that requires transitive it (e.g. java.sql). Otherwise, even while not strictly needed, it should be explicitly required. This approach clarifies the system's structure and also future-proofs the module declaration for various refactorings.


Last update: September 14, 2021


Current Tutorial
Implied Readability with requires transitive
Next in the Series

Previous in the Series: Optional Dependencies with requires static

Next in the Series: Qualified exports and opens