skip to content

When and why would you fork the compiler with meminitial/maxmem, and how does the plugin's incremental compilation work?

level: seniorimportance: nice to knowfreq 30%

answer

  1. fork=true → separate javac JVM
  2. meminitial=-Xms, maxmem=-Xmx
  3. memory args ignored without fork
  4. incremental = recompile stale only
  5. clean fixes flaky partial builds

basics

~10 s

Set <fork>true</fork> to run javac in a separate JVM, then size it with <meminitial> and <maxmem> for big modules. Incremental compilation (the default) only recompiles changed/stale classes instead of everything.

solid answer

~40 s

By default the compiler plugin runs javac in-process (in Maven's own JVM). With `<fork>true</fork>` it spawns a separate javac process, which lets you give the compiler its own heap via `<meminitial>` (-Xms) and `<maxmem>` (-Xmx), and pass custom `<compilerArgs>`/`<executable>` (a specific javac). You fork for very large modules that would otherwise blow Maven's heap, or to use a different JDK's javac than the one running Maven. Forking adds JVM-startup overhead so it's not free. Incremental compilation: since plugin 3.1, it tracks file state under target and recompiles only sources whose `.class` is missing or older, plus dependents, instead of the whole module. `<useIncrementalCompilation>` controls it; staleness/inputFileEnding detection has historically been imperfect, so flaky rebuilds are sometimes fixed with a clean.

code

xml · 6 lines
xml
<configuration>
  <fork>true</fork>
  <meminitial>128m</meminitial>
  <maxmem>512m</maxmem>
  <useIncrementalCompilation>true</useIncrementalCompilation>
</configuration>

go deeper

for a junior

Knows the compiler can run in a separate process and that not everything is recompiled each build.

for a middle

Maps meminitial/maxmem to -Xms/-Xmx and knows incremental skips unchanged sources.

for a senior

Decides when forking is justified (heap exhaustion, custom javac) and accepts its startup cost; knows incremental staleness limits.

for a principal

Weighs build-capacity/performance trade-offs at scale, including when a module's size argues for forking, toolchains, or moving to Gradle.

## Forking the compiler By default `maven-compiler-plugin` invokes javac **in the same JVM that runs Maven**. Setting `<fork>true</fork>` runs javac as a **separate OS process**. Why fork: - **Heap isolation/sizing**: a huge module (tens of thousands of sources, or heavy annotation processors) can exhaust Maven's heap. A forked javac gets its own heap, sized by: - `<meminitial>` → javac's `-Xms` (initial heap), e.g. `128m`. - `<maxmem>` → javac's `-Xmx` (max heap), e.g. `512m`. - **Custom executable**: `<executable>` lets you point at a *specific* javac binary (forking is required for this), e.g. to compile with a different JDK than the one running Maven. - **Passing JVM/compiler args** that only make sense to a standalone compiler process. Cost: forking adds **JVM startup latency** per compile execution, so for small/normal modules it usually slows the build. Use it only when needed. ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <fork>true</fork> <meminitial>128m</meminitial> <maxmem>512m</maxmem> </configuration> </plugin> ``` Note: `<meminitial>`/`<maxmem>` are **ignored unless `<fork>true</fork>`** — they only apply to the separate process. ## Incremental compilation The plugin does **not** recompile everything every build. It compares source files to their existing outputs under `target/classes` and recompiles only those that are **stale** (output missing or older than source) plus affected dependents. Relevant config: - `<useIncrementalCompilation>` — enable/disable incremental behaviour (default true in modern versions). - `<staleMillis>` — granularity for the timestamp comparison. Limitation: staleness detection is timestamp-based and has historically had edge cases (deleted classes, changed dependencies, generated sources), so a confusing partial build is sometimes resolved with `mvn clean`. It is far less sophisticated than Gradle's incremental compiler, which is one reason teams move to Gradle for very large codebases. ## Putting it together Fork + memory tuning addresses *capacity* (can the compile run at all); incremental compilation addresses *speed* (don't redo unchanged work). They are independent knobs.

  • Do <meminitial> and <maxmem> have any effect without forking?
    No. They size the separate javac process, so they are ignored unless <fork>true</fork> is set; in-process compilation shares Maven's own heap.
  • What is a practical downside of fork=true for ordinary modules?
    It adds JVM startup overhead per compile execution, usually making small/normal builds slower.

saying these in an interview costs you the question

  • Saying meminitial/maxmem tune Maven's heap directly — they only affect a forked javac process.
  • Claiming forking always speeds up compilation — it adds startup cost.
  • Believing incremental compilation is bulletproof; timestamp edge cases sometimes need a clean.

context