You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Default incremental compilation recompiles the whole source set on any change (JDK < 23, no annotation processor)Β #1085
With the default incremental configuration and no annotation processor on the classpath, changing a single source file recompiles the entire source set instead of only the modified file, on JDK < 23. This contradicts the documented default and defeats incremental compilation for the common editβcompile loop on the JDK 17 / 21 LTS releases.
If there is no annotation processor, then the default is options,dependencies,sources. It means that a full rebuild will be done if the compiler options or the dependencies changed, or if a source file has been deleted. Otherwise, only the modified source files will be recompiled.
Actual behaviour
On JDK < 23 with proc unset and no annotation processor present, any single-file change logs
[INFO] Recompiling all files because at least one source file changed.
and recompiles the whole source set.
Root cause
AbstractCompilerMojo#hasAnnotationProcessor() returns true unconditionally when proc is unset and the running JDK is older than 23 (because javac defaulted to -proc:full before Java 23). amendincrementalCompilation() then adds rebuild-on-add,rebuild-on-change to the default, forcing a full rebuild β even though no annotation processor is actually on the classpath.
On JDK 23+ the same method checks for real processor paths (PROCESSOR_CLASSES / PROCESSOR_MODULES) and correctly returns false, so the default behaves per-file.
Reproduction
Minimal project: 3 independent classes, no dependencies, compiler-plugin default config. Full compile, change one class, compile again β count recompiled .class files:
JDK
property
result
21
(default)
recompiles all 3
21
-Dmaven.compiler.proc=none
recompiles 1
21
-Dmaven.compiler.incrementalCompilation=sources
recompiles 1
25
(default)
recompiles 1
Note: on the released Maven 4.0.0-rc-5 runtime the incremental state is not persisted at all, so every compile is a full rebuild regardless of these settings. This reproduces on the maven-4.0.x / master SNAPSHOT lines (i.e. rc-6+), tested against maven-compiler-pluginmaster (4.0.0-beta-5-SNAPSHOT).
Expected fix
When proc is unset, gate the hasAnnotationProcessor() result on the presence of actual processor paths on all JDKs, not only JDK 23+. If no processor will run, a pre-23 -proc:full default still produces no generated files, so per-file incremental is safe β matching the documented default.
Regression test
I have an invoker IT (incremental-default-no-processor: three independent classes, default config, modify one between two compile invocations, assert only one file is recompiled). It is red on JDK < 23 and green on JDK 23+ before the fix, and green on all JDKs after. Happy to open a PR with the IT plus the fix.
src/it/incremental-default-no-processor compiles three independent classes with the default incremental configuration (no annotation processor), modifies one class between two compile invocations, and asserts that only the modified file is recompiled. Verified against master (4.0.0-beta-5-SNAPSHOT) on a Maven 4.0.x-SNAPSHOT runtime:
JDK 21 β red: Recompiling all files because at least one source file changed.
Digging deeper before proposing a patch β this turns out to be more of a design trade-off than a plain bug, so I'd like your steer before opening a PR.
What actually happens: when proc is unset the plugin passes no -proc option (Options.addComaSeparated skips a null value), so javac's own version-dependent default applies β -proc:full before Java 23, -proc:none (unless processors are configured) from Java 23 on.
On JDK < 23 with -proc:full, javac discovers annotation processors on the regular compile classpath (the classic Lombok-as-a-plain-<dependency> setup, no annotationProcessorPaths). In that case a processor really does run and rebuild-on-change is correct. hasAnnotationProcessor() can't tell that apart from "no processor at all" without inspecting the classpath, so it conservatively returns true. That's safe but pessimistic: a project with genuinely no processor (e.g. a plain test module) recompiles the whole source set on any change, which contradicts the documented default ("only the modified source files will be recompiled").
So the current behaviour isn't wrong β it just can't distinguish the two cases. Two ways forward:
Detect the actual processor. Scan annotationProcessorPaths (always) and, when -proc:full is in effect, the compile classpath too, for META-INF/services/javax.annotation.processing.Processor; drive hasAnnotationProcessor() off that. No behaviour change, per-file incremental whenever no processor is really present. Costs an IO scan (cacheable).
Document the conservatism. Keep the behaviour and note in the incrementalCompilation default javadoc that on JDK < 23 with proc unset the plugin assumes a processor may be present (because javac defaults to -proc:full and discovers classpath processors), and that proc=none or an explicit incrementalCompilation restores per-file.
(Forcing -proc:none by default when nothing is declared would align JDK < 23 with 23+, but it would stop classpath-discovered processors from running on JDK < 23 β a breaking change, even if javac already deprecates that pattern on 21+. So I'd rule that out.)
I've pushed an IT that reproduces the pessimistic case; it's happy to be narrowed to the provably-no-processor scenario for whichever direction you prefer. cc @desruisseaux, since this is your incremental-compilation code β which of (1)/(2) (or something else) would you accept?
The check for Java 23 was added in commit 3f01ff8 followed by a somewhat verbose cleanup in 3efa684 about one year ago. If I remember right, it was requested by someone maybe on the mailing list or on slack (I do not remember where exactly). I suggest to keep the current behaviour since it was requested, and document it instead. We can also said that the workaround for users on Java < 23 is to explicit set proc:none. It may not be worth to spend energy (and increase technical debt) for improving performance on old Java versions which will, hopefully, be progressively phased out.
Activity
ascheman commentedon Jul 19, 2026
I pushed a failing integration test that reproduces this, ready to fold into a PR together with the fix:
aschemaven:bugfix/1085-incremental-default-recompiles-allsrc/it/incremental-default-no-processorcompiles three independent classes with the default incremental configuration (no annotation processor), modifies one class between twocompileinvocations, and asserts that only the modified file is recompiled. Verified againstmaster(4.0.0-beta-5-SNAPSHOT) on a Maven 4.0.x-SNAPSHOT runtime:Recompiling all files because at least one source file changed.Compiling 1 modified source file.Happy to open the PR with the IT plus the
hasAnnotationProcessor()fix if the approach in the description looks right to you.ascheman commentedon Jul 19, 2026
Digging deeper before proposing a patch β this turns out to be more of a design trade-off than a plain bug, so I'd like your steer before opening a PR.
What actually happens: when
procis unset the plugin passes no-procoption (Options.addComaSeparatedskips anullvalue), sojavac's own version-dependent default applies β-proc:fullbefore Java 23,-proc:none(unless processors are configured) from Java 23 on.On JDK < 23 with
-proc:full,javacdiscovers annotation processors on the regular compile classpath (the classic Lombok-as-a-plain-<dependency>setup, noannotationProcessorPaths). In that case a processor really does run andrebuild-on-changeis correct.hasAnnotationProcessor()can't tell that apart from "no processor at all" without inspecting the classpath, so it conservatively returnstrue. That's safe but pessimistic: a project with genuinely no processor (e.g. a plain test module) recompiles the whole source set on any change, which contradicts the documented default ("only the modified source files will be recompiled").So the current behaviour isn't wrong β it just can't distinguish the two cases. Two ways forward:
annotationProcessorPaths(always) and, when-proc:fullis in effect, the compile classpath too, forMETA-INF/services/javax.annotation.processing.Processor; drivehasAnnotationProcessor()off that. No behaviour change, per-file incremental whenever no processor is really present. Costs an IO scan (cacheable).incrementalCompilationdefault javadoc that on JDK < 23 withprocunset the plugin assumes a processor may be present (becausejavacdefaults to-proc:fulland discovers classpath processors), and thatproc=noneor an explicitincrementalCompilationrestores per-file.(Forcing
-proc:noneby default when nothing is declared would align JDK < 23 with 23+, but it would stop classpath-discovered processors from running on JDK < 23 β a breaking change, even ifjavacalready deprecates that pattern on 21+. So I'd rule that out.)I've pushed an IT that reproduces the pessimistic case; it's happy to be narrowed to the provably-no-processor scenario for whichever direction you prefer. cc @desruisseaux, since this is your incremental-compilation code β which of (1)/(2) (or something else) would you accept?
desruisseaux commentedon Jul 20, 2026
The check for Java 23 was added in commit 3f01ff8 followed by a somewhat verbose cleanup in 3efa684 about one year ago. If I remember right, it was requested by someone maybe on the mailing list or on slack (I do not remember where exactly). I suggest to keep the current behaviour since it was requested, and document it instead. We can also said that the workaround for users on Java < 23 is to explicit set
proc:none. It may not be worth to spend energy (and increase technical debt) for improving performance on old Java versions which will, hopefully, be progressively phased out.[apache#1085] Document conservative incremental default on JDK < 23
[apache#1085] Add IT verifying proc=none per-file incremental on all β¦
[apache#1085] Address review: one clause per line, drop non-breaking β¦
[#1085] Document conservative incremental default on JDK < 23
[#1085] Add IT verifying proc=none per-file incremental on all JDKs
[#1085] Address review: one clause per line, drop non-breaking spaces