We've written a pretty cool Gradle plugin I wanted to share.
It turns out if you native-image the Java and Kotlin compilers, you can experience a serious gain, especially for "smaller" projects (under 10,000 classes).
By compiling the compiler with native image, JIT warmup normally experienced by Gradle/Maven et al is skipped. Startup time is extremely fast, since native image seals the heap into the binary itself. The native version of javac produces identical outputs from inputs. It's the same exact code, just AOT-compiled, translated to machine code, and pre-optimized by GraalVM.
Of course, native image isn't optimal in all cases. Warm JIT still outperforms NI, but I think most projects never hit fully warmed JIT through Gradle or Maven, because the VM running the compiler so rarely survives for long enough.
Elide (the tool used by this plugin) also supports fetching Maven dependencies. When active, it prepares a local m2 root where Gradle can find your dependencies already on-disk when it needs them. Preliminary benchmarking shows a 100x+ gain since lockfiles prevent needless re-resolution and native-imaging the resolver results in a similar gain to the compiler.
We (the authors) are very much open to feedback in improving this Gradle plugin or the underlying toolchain. Please, let us know what you think!
jart•1d ago
For example, what does resolution mean? Does that mean fetching the pom.xml files from sonatype to figure out the dependency graph? Don't those HTTP requests normally go fast? Is Elide sort of like setting up an HTTP caching proxy between corp and sonatype?
sgammon•1d ago
Basically, you install this plugin, and it tells the `JavaCompile` tasks normally used by Gradle to `isFork = true` and use `elide javac -- ...` for the compile command.
So, javac invocations travel over the CLI instead of through the Tooling API, which Gradle normally uses, and which is effectively running in Gradle's daemon VM, and thus is subject to JIT warmup.
Through Elide, since it's a native binary, all that JIT warmup is skipped, and you are effectively choosing to balance more toward quick startup and wall-clock time (with many small calls) instead of waiting for JIT warmup to make the Gradle daemon fast with javac.
Otherwise, it's a completely identical javac experience. Running `elide javac -help` produces identical help output. Identical inputs should produce identical outputs, and we build it at JDK 24 so it can support `--source/--target/--release X` and friends for anything older down to JDK 8.
> For example, what does resolution mean? Does that mean fetching the pom.xml files from sonatype to figure out the dependency graph?
It means resolving the graph from declared (direct) dependencies, downloading pom.xml metadata, downloading JARs, unpacking it all to disk, and providing a local `.m2` Maven-compliant root, sort of like Node does for `node_modules/`. Ours lives in `.dev/dependencies/m2` once you run `elide install`.
Elide embeds Maven's resolver, so resolution semantics are identical to Maven's. We do some small optimizations to make fetching fast (i.e. initializing related classes at build time, that sort of thing), but honestly not much. Just building Maven's resolver like this yields a major gain, and not messing with it preserves expected behavior.
Since Elide emits these deps in a Maven Local-style root, Gradle just finds them on disk when it needs them, so it doesn't need to engage its resolver or fetcher at all.
> Don't those HTTP requests normally go fast?
It is worth noting that Gradle seems confined to HTTP/1.1 and poor connection pooling even today, so it's not that hard to beat.
> Is Elide sort of like setting up an HTTP caching proxy between corp and sonatype?
We don't proxy or anything, this fetching still happens through normal Maven Central unless configured otherwise in your `elide.pkl` manifest. For now, deps are placed in the local project, but we want to move to a central cache and link like modern NPM installers do.
sgammon•1d ago