skip to content

What causes a reactor cyclic dependency error, and how do you resolve it architecturally?

level: seniorimportance: should knowfreq 35%

answer

  1. topological sort needs a DAG
  2. A->B->A = cyclic reference error
  3. extract shared 'common' module
  4. dependency inversion via interface
  5. cycle can hide in parent/plugin

basics

~20 s

A cycle happens when module A depends on B and B depends (directly or transitively) back on A. The reactor can't topologically sort a cyclic graph, so it fails. Fix it by extracting shared code into a new third module both can depend on.

solid answer

~40 s

The reactor needs a directed acyclic graph (DAG) to compute build order. If inter-module dependencies form a cycle — A → B → A, possibly via several hops — there is no valid order and Maven aborts with "The projects in the reactor contain a cyclic reference." Cycles usually signal a design problem: two modules sharing types that pull each other in both directions. The standard fixes are to (1) extract the common types/interfaces into a new lower-level module that both depend on, (2) invert a dependency using interfaces (dependency inversion) so one direction disappears, or (3) merge two modules that are too entangled to separate. Tightening module boundaries and keeping the graph shallow prevents recurrence. Note cycles are forbidden between modules even though within a single module Java allows mutual class references.

code

xml · 6 lines
xml
<!-- Break app<->core cycle: both depend on a new 'common' module -->
<dependency>
  <groupId>com.example</groupId>
  <artifactId>common</artifactId>
  <version>1.0</version>
</dependency>

go deeper

for a junior

Recognizes the cyclic reference error message and that it must be fixed, not configured around.

for a middle

Understands cycles can be transitive and that ordering can't be reused to fix them.

for a senior

Applies extract-shared-module and dependency-inversion fixes and spots cycles hidden in parent/plugin edges.

for a principal

Establishes layered module architecture and review gates so back-edges are caught before they reach the reactor.

## Why cycles break the reactor The reactor computes build order via **topological sort**, which is only defined on a **directed acyclic graph (DAG)** — a graph with no cycles. If module dependencies loop back on themselves, no module can be "first," and Maven cannot order the build. It fails fast with a message like: ``` The projects in the reactor contain a cyclic reference: ... ``` ## What creates a cycle - **Direct:** `app` depends on `core`, and `core` depends on `app`. - **Transitive:** `a` → `b` → `c` → `a`. Each edge looks reasonable alone; together they loop. - Edges come from `<dependency>`, `<parent>`, and plugin references, so a cycle can hide in a parent/plugin relationship, not just a code dependency. Within one module, Java happily lets two classes reference each other — there's no separate compilation unit. The cycle problem is specifically at the **module** level because each module is a separately-ordered build unit. ## Architectural fixes 1. **Extract a shared module.** If A and B both need some common types, move those types into a new lower module `common`, and have both A and B depend on `common`. The mutual edges become two downward edges — no cycle. 2. **Dependency inversion.** Define an interface in the module that should NOT depend on the other, and have the other implement it. This reverses one arrow so the loop opens. 3. **Merge the modules.** If two modules are so coupled that you keep fighting cycles, they may genuinely be one module. ## Example of the fix ``` Before (cycle): app <--> core After (DAG): app -> core \ / v v common ``` ```xml <!-- common/pom.xml has no dependency on app or core --> <!-- app and core both declare: --> <dependency> <groupId>com.example</groupId> <artifactId>common</artifactId> <version>1.0</version> </dependency> ``` ## Prevention Keep the module graph shallow and layered (e.g. domain → service → api → app), review new cross-module dependencies, and treat a proposed back-edge as a design smell.

  • Why does Java allow mutual class references but Maven forbid mutual module references?
    Within a module everything compiles together as one unit. Modules are separately-ordered build units, so the reactor needs a DAG to pick a build order.
  • Can a cycle come from something other than <dependency>?
    Yes — parent POM references and plugin (or plugin-dependency) references are also edges, so a cycle can hide in those relationships.

You can't decide who enters a doorway first if each person insists the other must go before them — someone has to step aside (extract a shared module).

saying these in an interview costs you the question

  • Trying to fix a cycle by reordering <modules>
  • Claiming Maven will pick an arbitrary order to resolve the cycle
  • Adding scope=provided to hide the cycle instead of breaking it

context