33

Even if I only change one of my classes, Maven always recompiles all of them. I use this plugin configuration:

<plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
      <configuration>
        <staleMillis>1</slateMillis>
        <useIncrementalCompilation>true</useIncrementalCompilation>
      </configuration>
    </plugin>
</plugins>

This happens with mvn compile, mvn package and mvn install.

Of course this is not a problem if you have 10-15 files. However, I have more than one thousand source files and it takes a lot of time.

Does the Maven compiler plugin have some hidden settings to recompile only modified files? Are there any workarounds?

Jens Bannmann
  • 4,845
  • 5
  • 49
  • 76
Torsten
  • 21,726
  • 5
  • 24
  • 31
  • 1
    Torsten, please take note of the warning comments to the answer you accepted; this is *not* a case of a true/false mixup. For background, see my answer. You may want to reconsider which answer to accept. – Jens Bannmann Apr 06 '18 at 21:05
  • 1
    Probably bad news: See bug [MCOMPILER-205](https://issues.apache.org/jira/browse/MCOMPILER-205). – Jens Birger Hahn Jun 07 '13 at 13:38
  • 1
    Looks like the Maven developers tried to develop an "improved" incremental compilation that doesn't work. When you set `false` you revert to the standard incremental compilation provided by javac, which is what this question is asking for. The accepted answer is correct and requires no warning qualifications. – robert Nov 02 '18 at 13:29
  • You could also try to use [ecj over javac](/q/33164976), differences explained [here](/q/3061654). IntelliJ bundles it under _Java Compiler_ setting, though it already tracks changes itself. When switching reload your maven projects, e.g. not to run in [this](/q/65128763) lombok issue. – cachius Nov 21 '22 at 17:08

2 Answers2

58

Summary

While you can tell Maven "to recompile only modified files", doing so will lead to wrong results. The default behavior is not a bug, but an intentional design decision.


What useIncrementalCompilation really does

The documentation on this topic is, to put it mildly, not optimal. Here is what really happens (based on AbstractCompilerMojo source from maven-compiler-plugin 3.3):

  • useIncrementalCompilation set to false (not recommended)
    • This will only compile source files which are newer than their corresponding class files, i.e. which have been changed since the last compilation process. As @Ivan notes in a comment on another answer, this will not recompile other classes which use the changed class, potentially leaving them with references to methods that no longer exist, leading to errors at runtime.
  • useIncrementalCompilation set to true (default)
    • To handle the problem outlined above, in this mode the compiler plugin will determine whether
      • any JAR files the current module depends on have been changed in the current build run, or
      • any source file was added, removed or changed since the last compilation.
    • If this is the case, the compiler plugin intentionally recompiles all sources, printing Changes detected - recompiling the module!

So in summary, useIncrementalCompilation should always be left at the default of true.


Why it does not do something else

Understandably, one might ask: why does the plugin not determine which classes are affected by the changes, and recompile only those classes? In the comments on MCOMPILER-205, Maven developer Robert Scholte gave a brief rationale and later confirmed the following detailed explanation:

If any source file has been changed or removed, all files are deleted and recompiled. The reason for this is that simply recompiling everything with the default java compiler is quite fast, likely much faster than the alternative, which would look similar to this:

  1. detect all changed files
  2. analyze all source files to map all relations between classes
  3. calculate all affected files
  4. recompile affected files

However, as Robert also writes, recompiling everything is probably not necessary if the project uses the Eclipse compiler, which does this analysis. But for today's Maven users, this is a moot point as maven-compiler-plugin does not yet change its behavior based on the choice of compiler.

Community
  • 1
  • 1
Jens Bannmann
  • 4,845
  • 5
  • 49
  • 76
  • 5
    I think you have misread the developers comment. I was scratching my head at first but I think what he meant was that the *default* incremental compilation provided by javac is fast. I very much doubt he meant that recompiling *everything* each time you make a change is preferable. You only need a project of >100 source-files to show that's nonsense. – robert Nov 02 '18 at 13:32
  • @robert I asked for clarification in the linked issue and have updated my answer with the detailed explanation the developer confirmed. Now of course their assumption ("likely much faster than the alternative") may turn out to be wrong. I'm pretty sure that if you submit a patch and a demo project that proves this, the Maven team will gladly accept it. – Jens Bannmann Nov 15 '18 at 17:14
  • @JensBannmann I'd be happy to merge a PR from you with this information. – Michael-O Aug 24 '20 at 16:29
  • @JensBannmann Does the resource folder also be compiled? Bcz when I modify a class and then compile it again resource folder hash is also changing. –  Aug 11 '21 at 07:32
41

https://issues.apache.org/jira/browse/MCOMPILER-209

Use it with Bulgarian notation (yes <-> no)

<useIncrementalCompilation>false</useIncrementalCompilation> means true and vice versa

Brian Riehman
  • 27,020
  • 2
  • 24
  • 17
wapgui
  • 742
  • 8
  • 10
  • 8
    Keep in mind that with this setting it does the simple incremental compilation, but it is not very usefull since it doesn't recompile dependent classes. So for example if you have class A calling a method of class B, and you modify signature of a method in B, maven will recompile only B and the compilation will suceed, but now A has a reference to the non-existent method in B. – Ivan Apr 05 '15 at 09:12
  • 10
    So this setting can be thought of as "useSmartIncrementalCompilation", so when we set it to false, we only get the basic (and dangerous) one I descibed above. In maven 3 the "smart" one seems to be broken, and instead of calculating the right dependent classes, if a single class in a project is changed it marks all classes as dependent classes, effectively making the compilation non-incremental. – Ivan Apr 05 '15 at 09:17
  • 5
    Dear wapgui, your answer is misleading, see the comments by @Ivan above. Please add a warning to the answer. – Jens Bannmann Mar 18 '18 at 10:10
  • No warning is required. This is the correct answer because it provides default javac behaviour, which is what is sought. Of course it should go without saying that if you're building a release you start clean but for incremental development the risks are understood and accepted as a fair tradeoff. – robert Nov 02 '18 at 13:33
  • 2
    @robert I'm afraid I disagree. Nothing in the question indicates that "default javac behaviour ... is sought" or that "risks are understood and accepted as a fair tradeoff." It is also not noted in the answer, so that's why I recommended adding a warning to the answer. – Jens Bannmann Nov 15 '18 at 17:19
  • This *is* the default javac behaviour. OP just wants to know what happened to it. – robert Nov 15 '18 at 17:23
  • @wapgui May you provide a reference explaining / coining the term "Bulgarian notation"? Never heard of this before, neither have google nor bing. – Philzen Apr 21 '21 at 00:52