All work
PerformanceSep 2022 – Mar 2024

Incremental build engine — outperforming Gradle

Custom timestamp-based incremental compilation cut a no-change test run from 122 seconds to 6 seconds and an isolated action change to 7 seconds.

No-change: 122s → 6s · Action change: 189s → 7s · Gradle eliminated


The problem

ACCELQ compiled test scenarios using Gradle before each execution run. For a realistic suite of 246 scenarios and 690 test cases:

• No change at all: still took 122 seconds just to confirm nothing needed rebuilding • Shared action change: 189 seconds • Independent action change: ~150 seconds

Gradle itself added ~300 MB of process overhead and had an irreducible startup cost regardless of what changed. As test suites grew, this was becoming a meaningful bottleneck in the feedback loop.

What I built

Designed and implemented a custom incremental compilation system using code-generation timestamps. The system understands ACCELQ's specific dependency graph (which actions a scenario depends on) and recompiles only what changed.

Removed Gradle entirely — replaced with direct javac invocation. Implemented lazy loading for element classes via Weld CDI extension using a custom extension, reducing startup memory by ~200 MB. Fixed memory leaks in context, scenario, and element creation at runtime.

All benchmarks run on 246 scenarios / 690 test cases on a standard machine.

Approaches considered

Gradle incremental builds — already in use; insufficient because Gradle's change detection doesn't model ACCELQ's action-to-scenario dependency graph correctly
Bazel / Buck — considered: excellent incremental builds but significant toolchain change and learning curve for the team
Turbopack / esbuild-style caching — not applicable: the build target is JVM bytecode, not JavaScript bundles
Custom timestamp-based incremental system — chosen: understands ACCELQ's specific compilation dependency model, zero new tooling overhead

Architecture

On each run: read code-generation timestamps from previous build → compare to source file modification times → identify changed actions and which scenarios depend on them → invoke javac only for changed classes and their dependents → Weld CDI lazy-loads element classes on first use rather than eagerly at startup. The dependency graph is maintained as a lightweight JSON file alongside the compiled output.

Result

No-change scenario: 122.809s → 6.289s (19× faster). Shared action change: 189.255s → 38.765s (5× faster). Independent action change: 7.252s. First-time build: 236.512s → 179.746s. Startup memory reduced by ~500 MB (lazy loading + Gradle removal combined).

JavajavacWeld CDIBuild Systems
Back to all work