skip to content

Design a CI strategy that builds only the modules affected by a pull request in a large reactor. Which selectors and flags would you use, and what are the correctness risks?

level: seniorimportance: should knowfreq 35%

answer

  1. diff -> module dirs
  2. -pl changed -amd
  3. -am for fresh upstream
  4. full-build fallback on parent/BOM
  5. nightly full build for drift

basics

~20 s

Detect changed module dirs from the git diff, then run mvn verify -pl <changed> -amd (and -am if upstreams may need rebuilding). This builds the touched modules plus everything that depends on them, skipping unaffected modules.

solid answer

~50 s

I'd compute the set of changed modules from the PR's git diff (mapping changed file paths to module directories), then run `mvn verify -pl <changed-list> -amd` so the reactor builds the changed modules and all their downstream dependents — the true blast radius. Add `-am` if changed modules depend on siblings that must be freshly built rather than pulled from a stale local/remote repo. Pair with `-T 1C` for parallelism. Correctness risks: (1) a change to the parent/aggregator pom, a BOM, or shared properties affects *everything* — fall back to a full build for those paths; (2) non-pom changes (e.g. resource files, plugin config) that the diff-to-module mapping misses; (3) dependencies expressed dynamically or via dependency-management that the naive mapping ignores; (4) relying on artifacts in a shared cache that may be stale. So: incremental for normal module edits, full build as a guarded fallback for root/shared changes, and a periodic full build to catch drift.

code

bash · 7 lines
bash
CHANGED=$(git diff --name-only origin/main...HEAD \
  | sed -E 's#/[^/]*$##' | sort -u | paste -sd, -)
if git diff --name-only origin/main...HEAD | grep -qE '^(pom.xml|bom/|\.mvn/)'; then
  mvn -T 1C verify            # parent/BOM/shared change -> full build
else
  mvn -T 1C verify -pl "$CHANGED" -amd
fi

go deeper

for a junior

Understand the idea: build changed modules and their dependents instead of everything.

for a middle

Map diff to modules and apply -pl with -amd/-am correctly.

for a senior

Reason about correctness gaps (BOM/parent/managed deps) and design the full-build fallback plus parallelism.

for a principal

Own the org-wide build-affected strategy: tooling for accurate impact detection, caching policy, drift detection, and trade-offs between speed and safety.

## Goal Reduce CI time on a big reactor by building only what a change can affect, without missing breakage. ## Step 1 — determine changed modules From `git diff --name-only origin/main...HEAD`, map each changed path to the module it lives in (the nearest ancestor directory containing a `pom.xml`). Deduplicate into a comma-separated selector list. ## Step 2 — pick the reactor flags - `-pl <changed>`: limit to changed modules. - `-amd` (**also-make-dependents**): add every module that depends on a changed module — this is the affected blast radius you must re-test. - `-am` (**also-make**): add upstream dependencies if they must be built from source (e.g. you can't trust a remote snapshot/cache). Often you can skip `-am` if upstreams are unchanged and resolvable from the repo. ```bash CHANGED=$(git diff --name-only origin/main...HEAD \ | sed -E 's#/[^/]*$##' | sort -u | paste -sd, -) mvn -T 1C verify -pl "$CHANGED" -amd ``` ## Step 3 — guard the dangerous cases (full-build fallback) Some changes invalidate the incremental assumption and require a **full build**: - the **root/aggregator pom** or a **parent pom** (changes properties, dependencyManagement, plugin versions for all children), - a **BOM** module imported via `<scope>import</scope>`, - shared build config (e.g. checkstyle/enforcer rules), CI scripts, or `.mvn/` config. Detect these paths and switch to `mvn verify` (no `-pl`). ## Step 4 — catch drift Run a **scheduled full clean build** (nightly) so accumulated mapping gaps or cache staleness surface regularly, not just at release time. ## Correctness risks summarized - **Transitive/managed deps**: a module may be affected through dependencyManagement or a property even without a direct `<dependency>` edit — `-amd` only follows declared reactor dependencies. - **Resource/plugin changes** not captured by directory mapping. - **Stale cached artifacts** substituting for what should be rebuilt. - **Cross-cutting parent changes** — handle via fallback. ## Why -amd is the keystone Building only changed modules is unsafe: a change in `core` can break `web` even though `web`'s files didn't change. `-amd` ensures consumers are re-verified. `-am` is about *building* prerequisites; `-amd` is about *protecting* consumers.

  • Why is -amd essential rather than just -pl on changed modules?
    A change in a low-level module can break consumers whose files didn't change. -amd pulls in all dependents so they're re-tested; without it CI would pass while downstream is broken.
  • Which kinds of changes should force a full build instead of incremental?
    Changes to the root/parent pom, an imported BOM, shared build/enforcer config, or .mvn settings — they affect all modules, so the changed-module mapping understates the impact.
  • How do you stop incremental builds from masking long-term drift?
    Run a scheduled full clean build (e.g. nightly) so mapping gaps, cache staleness, and dependency-management effects surface regularly.

saying these in an interview costs you the question

  • Building only changed modules with -pl and no -amd (misses downstream breakage)
  • Assuming a parent-pom edit only affects the parent module
  • Trusting cached/remote snapshot artifacts without a periodic clean full build

context