Skip to content

Make rebuild-on-change the default incremental compilation strategy - #1143

Open
gnodet wants to merge 2 commits into
masterfrom
fix/default-rebuild-on-change
Open

gnodet wants to merge 2 commits into
masterfrom
fix/default-rebuild-on-change

Conversation

@gnodet

@gnodet gnodet commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Problem

The current default incremental compilation configuration for Java >= 23 / no annotation processor projects is options,dependencies,sources. This allows partial builds — only modified source files are passed to javac. While faster, this is unsafe: if Foo.java changes a method signature, Bar.java (which calls Foo) is not recompiled, potentially leaving a stale Bar.class that causes NoSuchMethodError at runtime.

This is the same correctness risk that made useIncrementalCompilation=false "not recommended" in 3.x.

Notably, the annotation-processor path already defaults to rebuild-on-change — so the behavior was inconsistent: safe with processors, potentially unsafe without.

Change

Add rebuild-on-change to the base default, making it options,dependencies,sources,rebuild-on-change for all projects regardless of annotation processor presence.

Users who want faster (but potentially unsafe) per-file recompilation can still opt in explicitly with:

<incrementalCompilation>options,dependencies,sources</incrementalCompilation>

Relationship to PR #1123

This was identified during the review discussion on #1123, where the Javadoc for incrementalCompilation was being clarified. The Javadoc explicitly documents the limitation: "the current compiler-plugin does not detect structural changes other than file addition or removal". Making the default safe is the natural complement to that documentation.

Fixes MCOMPILER-563 (partial — correctness concern).

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sound change — making the default safe-by-default is the right call, and the opt-out path is clearly documented.

One Javadoc grammar nit inline.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet
gnodet requested a review from desruisseaux September 28, 2026 19:16
@gnodet
gnodet marked this pull request as draft September 28, 2026 21:55
@desruisseaux

Copy link
Copy Markdown
Contributor

Just a clarification on the following:

Notably, the annotation-processor path already defaults to rebuild-on-change — so the behavior was inconsistent: safe with processors, potentially unsafe without.

In my understanding, the rational for rebuilding all classes when annotation processing is present was not for safety. It is because annotation processor can collect information about annotated elements, and therefore may have incomplete information if we don't rebuild all. Example: a @IncludeMeInSomeList annotation and a processor which, after compilation, generates a file with all elements having this annotation. The processor needs all source files at compilation time even if these files are not impacted by any change. Even if we implemented an accurate tracking of dependencies ("IDE-style incremental build"), we would still need to rebuild all files when an annotation processor is present.

@gnodet

gnodet commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Just a clarification on the following:

Notably, the annotation-processor path already defaults to rebuild-on-change — so the behavior was inconsistent: safe with processors, potentially unsafe without.

In my understanding, the rational for rebuilding all classes when annotation processing is present was not for safety. It is because annotation processor can collect information about annotated elements, and therefore may have incomplete information if we don't rebuild all. Example: a @IncludeMeInSomeList annotation and a processor which, after compilation, generates a file with all elements having this annotation. The processor needs all source files at compilation time even if these files are not impacted by any change. Even if we implemented an accurate tracking of dependencies ("IDE-style incremental build"), we would still need to rebuild all files when an annotation processor is present.

Yes, the only way is to have the annotation processors provide some meta-information about their processing and whether they are safe to not rebuild all. This is what is implemented with

.

However, the main point is that I think this is wrong to provide a default behaviour which is unsafe in the default basic use case.

@gnodet
gnodet marked this pull request as ready for review October 6, 2026 13:42
@gnodet
gnodet force-pushed the fix/default-rebuild-on-change branch from 6ec444b to 1564fdb Compare October 6, 2026 13:43
@gnodet gnodet added this to the 4.0.0-beta-6 milestone Oct 9, 2026

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Javadoc update in this PR is partially correct but misses one case introduced by PR #1136 (merged Oct 4).

amendincrementalCompilation() has two branches, not one:

  1. Uncertain case (Java < 23, proc unset, no processor path): adds REBUILD_ON_ADD + REBUILD_ON_CHANGE to the base → result matches "same as above with the addition of rebuild-on-add" ✅
  2. Confirmed case (proc explicitly set to non-none, or Java ≥ 23 with explicit processor paths): calls aspects.clear() then sets NONE → incremental compilation is disabled entirely, not "same as above + additions" ❌

The updated Javadoc only describes the uncertain sub-case. It says the annotation-processor default is "same as above with the addition of rebuild-on-add", which is inaccurate when a processor is confirmed — in that branch the result is NONE, not a superset of the base config.

Suggestion: Rebase on master to pick up #1136, then expand the annotation-processor paragraph to cover both sub-cases:

  • When the processor is uncertain (Java < 23, no explicit proc path): options,dependencies,sources,rebuild-on-change,rebuild-on-add
  • When the processor is confirmed (explicit proc, or Java ≥ 23 with processor paths): none (full incremental disabled)

This review was generated by an AI agent, Hermès on behalf of @gnodet.

Comment thread src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java Outdated

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Javadoc update in this PR is partially correct but misses one case introduced by PR #1136 (merged Oct 4).

amendincrementalCompilation() has two branches, not one:

  1. Uncertain case (Java < 23, proc unset, no processor path): adds REBUILD_ON_ADD + REBUILD_ON_CHANGE to the base → result matches "same as above with the addition of rebuild-on-add" ✅
  2. Confirmed case (proc explicitly set to non-none, or Java ≥ 23 with explicit processor paths): calls aspects.clear() then sets NONE → incremental compilation is disabled entirely, not "same as above + additions" ❌

The updated Javadoc only describes the uncertain sub-case. It says the annotation-processor default is "same as above with the addition of rebuild-on-add", which is inaccurate when a processor is confirmed — in that branch the result is NONE, not a superset of the base config.

Suggestion: Rebase on master to pick up #1136, then expand the annotation-processor paragraph to cover both sub-cases:

  • When the processor is uncertain (Java < 23, no explicit proc path): options,dependencies,sources,rebuild-on-change,rebuild-on-add
  • When the processor is confirmed (explicit proc, or Java ≥ 23 with processor paths): none (full incremental disabled)

This review was generated by an AI agent, Hermès on behalf of @gnodet.

Comment thread src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java Outdated
gnodet added 2 commits October 9, 2026 10:46
Without rebuild-on-change, a partial build (only modified source files
recompiled) can leave stale .class files for classes that depend on a
changed class. For example, if Foo.java changes its method signature,
Bar.java (which calls Foo) is not recompiled, potentially causing
NoSuchMethodError at runtime.

The annotation-processor path already defaulted to rebuild-on-change;
this change makes the no-processor default consistent and safe.
Users who prefer faster (but potentially unsafe) per-file recompilation
can set incrementalCompilation=options,dependencies,sources explicitly.
- Update incremental-proc-none-per-file IT to explicitly set
  incrementalCompilation=options,dependencies,sources so it tests the
  opt-in per-file strategy rather than the new default (rebuild-on-change).
  The default was changed by this PR; the IT now documents the explicit
  opt-in path for users who want faster but potentially unsafe per-file
  compilation.

- Fix Javadoc grammar nit: add missing 'or' in the list
  'if the compiler options or the dependencies changed, or if a source
  file has been deleted, or if any source file has been modified'.
@gnodet
gnodet force-pushed the fix/default-rebuild-on-change branch from cd1bb3a to 44552ac Compare October 9, 2026 08:46

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR correctly changes the no-processor default to include rebuild-on-change, but the final Javadoc paragraph (unchanged from the old code) is now stale and internally inconsistent with the code above it.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

* doing a full rebuild on any change even when no processor is actually present.
* and would discover processors on the compile classpath.
* Projects on Java &lt; 23 that use no annotation processor can restore per-file recompilation
* by setting {@link #proc} to {@code "none"} (or by setting this {@code incrementalCompilation} property explicitly).</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Stale Javadoc — proc=none no longer restores per-file recompilation

This paragraph survived unchanged from the old code, but is now incorrect after the base-default change at line 645.

Previously, proc=none caused hasAnnotationProcessor() to return false, which selected the no-processor default of options,dependencies,sources — no rebuild-on-change, so per-file recompilation worked.

With this PR, the no-processor default is now options,dependencies,sources,rebuild-on-change (line 645). Setting proc=none still picks the no-processor branch, but that branch now includes rebuild-on-change, so a full rebuild is triggered on every source change.

The IT diff confirms this: incremental-proc-none-per-file was updated to explicitly set <incrementalCompilation>options,dependencies,sources</incrementalCompilation> because proc=none alone is no longer sufficient, with the IT description stating: "users who want faster (but potentially unsafe) per-file compilation must opt in explicitly."

The paragraph should be updated to remove the proc=none shortcut and only recommend the explicit incrementalCompilation property:

Suggested change
* by setting {@link #proc} to {@code "none"} (or by setting this {@code incrementalCompilation} property explicitly).</p>
* Projects on Java &lt; 23 that use no annotation processor can restore per-file recompilation
* by setting this {@code incrementalCompilation} property explicitly to {@code "options,dependencies,sources"}.</p>

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR correctly changes the no-processor default to include rebuild-on-change, but the final Javadoc paragraph (unchanged from the old code) is now stale and internally inconsistent with the code above it.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

* doing a full rebuild on any change even when no processor is actually present.
* and would discover processors on the compile classpath.
* Projects on Java &lt; 23 that use no annotation processor can restore per-file recompilation
* by setting {@link #proc} to {@code "none"} (or by setting this {@code incrementalCompilation} property explicitly).</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Stale Javadoc — proc=none no longer restores per-file recompilation

This paragraph survived unchanged from the old code, but is now incorrect after the base-default change at line 645.

Previously, proc=none caused hasAnnotationProcessor() to return false, which selected the no-processor default of options,dependencies,sources — no rebuild-on-change, so per-file recompilation worked.

With this PR, the no-processor default is now options,dependencies,sources,rebuild-on-change (line 645). Setting proc=none still picks the no-processor branch, but that branch now includes rebuild-on-change, so a full rebuild is triggered on every source change.

The IT diff confirms this: incremental-proc-none-per-file was updated to explicitly set <incrementalCompilation>options,dependencies,sources</incrementalCompilation> because proc=none alone is no longer sufficient, with the IT description stating: "users who want faster (but potentially unsafe) per-file compilation must opt in explicitly."

The paragraph should be updated to remove the proc=none shortcut and only recommend the explicit incrementalCompilation property:

Suggested change
* by setting {@link #proc} to {@code "none"} (or by setting this {@code incrementalCompilation} property explicitly).</p>
* Projects on Java &lt; 23 that use no annotation processor can restore per-file recompilation
* by setting this {@code incrementalCompilation} property explicitly to {@code "options,dependencies,sources"}.</p>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants