skip to content

What is `requires transitive`, and how does implied readability change what consumers of your module can see?

level: middleimportance: should knowfreq 58%

answer

  1. transitive = implied readability passed to consumers
  2. use it when A's types appear in your exported API
  3. plain requires does not chain (C->B->A: C can't read A)
  4. aggregator modules: java.se re-exports the SE set
  5. leaks the dep into your contract — use sparingly

basics

~20 s

requires transitive X means anyone who depends on your module automatically also gets to read module X, without listing it themselves. It is used when your public API exposes types from X, so callers can use those types.

solid answer

~50 s

Plain `requires X` is non-transitive: your module reads X, but a module that reads *you* does not automatically read X. `requires transitive X` adds **implied readability**: any module that `requires` yours implicitly also reads X, as if it had written `requires X` itself. You use it when your **exported API exposes types from X** — for example, a method that returns a `com.google.common.collect.ImmutableList`. Without `transitive`, callers could see your method signature but couldn't name the return type, forcing every consumer to add a redundant `requires`. `transitive` re-exports that dependency as part of your public contract. The classic example is `java.se`, an aggregator module that `requires transitive` the whole standard set so a single `requires java.se` pulls them all in. The trade-off: it leaks an implementation dependency into your API, so reserve it for types that genuinely appear in your exported signatures.

code

java · 11 lines
java
// module b: its public API returns a type from module a
module b {
    requires transitive a;   // a's types appear in b's exported signatures
    exports b.api;
}

// module c: gets readability of BOTH b and a from one line
module c {
    requires b;              // implied readability of a comes along
}
// In c: can name a.Thing returned by b's API, without 'requires a'

go deeper

for a junior

Recognizes the keyword and that it passes a dependency down to consumers.

for a middle

Defines implied readability, gives the API-leak rule (use when A's types appear in your exported signatures), and knows requires doesn't chain by default.

for a senior

Weighs the contract trade-off of re-exporting a dependency and explains the aggregator-module pattern (java.se).

for a principal

Sets library policy on which dependencies become part of the public contract via transitive, minimizing accidental coupling for downstream consumers.

## Recap: ordinary readability In JPMS, `requires X` grants your module **readability** of X — you can use X's exported types. Readability is **not transitive by default**: if module C `requires B` and B `requires A`, then C can read B but **cannot** read A. C would have to add its own `requires A`. ## The problem `requires transitive` solves Suppose module B exports an API method whose signature *mentions a type from A*: ```java // in module B (B requires A) public A.Thing makeThing(); // return type comes from module A ``` A consumer module C does `requires B` to call `makeThing()`. But the return type `A.Thing` lives in module A, which C does **not** read. So C cannot even name the variable that holds the result — its own code won't compile. Every consumer would be forced to add a `requires A` purely because B's *public* API leaks A's types. That is fragile and surprising. ## `requires transitive` — implied readability `requires transitive A` (written in module B) tells the system: **every module that reads B should implicitly also read A.** This is called **implied readability**. Now C just does `requires B` and automatically gets readability of A too — it can name `A.Thing` without ever mentioning A. Effectively, B **re-exports** its dependency on A as part of B's public contract. A becomes part of B's API surface. ## When to use it (and when not) Use `requires transitive A` **when types from A appear in B's exported (public) API** — return types, parameters, public fields, supertypes, thrown exceptions, generic bounds. That is the signal that callers genuinely need A to use B. Do **not** use it for purely internal dependencies. If A's types never escape B's exported signatures, plain `requires A` is correct; making it transitive needlessly leaks an implementation detail into your API and lets consumers depend on something they shouldn't. ## The aggregator-module pattern The JDK uses this to build **aggregator modules** — modules with no packages of their own that exist only to pull in a set of others. The prime example is `java.se`: ```java module java.se { requires transitive java.sql; requires transitive java.xml; requires transitive java.desktop; // ... the full Java SE set } ``` Because each is `transitive`, a single `requires java.se` in your module gives you readability of the entire standard API set, easing migration from the classpath. ## Mental model - `requires A` = "I use A internally." - `requires transitive A` = "I use A, and so will anyone who uses me, because A shows up in my public API."

  • How do you decide between `requires` and `requires transitive`?
    Ask whether types from the dependency appear in your module's *exported* API (return types, parameters, supertypes, etc.). If yes, use transitive so callers can name those types; if the dependency is purely internal, use plain requires.
  • What is an aggregator module?
    A module with no packages of its own that exists only to `requires transitive` a set of other modules, so a single requires pulls them all in. `java.se` is the canonical example.

Plain requires is borrowing a tool for yourself. requires transitive is co-signing: when you lend the tool to a friend, your friend is automatically trusted by the original owner too — because the tool is part of what you hand over.

saying these in an interview costs you the question

  • Believing plain requires chains through intermediate modules
  • Using transitive for purely internal dependencies (leaks the dep)
  • Confusing transitive (implied readability) with exports (package visibility)
  • Thinking transitive re-exports A's packages — it grants readability, A still controls its own exports

context