Elide bundles javac for Java 25; no separate JDK is needed.
Compile source files
elide javac -- -d out src/Hello.java
elide java -- -cp out Hellojavac options go after --. Without -d, class files land beside their sources. elide javac -- -help lists
every javac option.
Elide options
Elide options go before --:
| Option | Purpose |
|---|---|
--jar PATH | Pack the output into a JAR at PATH. |
--manifest FILE | Manifest for the --jar output. |
--max-errors N | Errors shown before the rest are counted; 0 shows all. Warnings are de-duplicated and never limited. |
--report-used-deps PATH | Write the classpath entries the compiler read, supertypes included (Bazel strict deps). |
--jdeps PATH | Write a Bazel .jdeps proto: each classpath entry explicit, implicit or unused, with the classes used from it and their SHA-256 hashes. |
--target-label LABEL | Target label recorded in the --jdeps proto. |
--unused-inputs PATH | Write the classpath entries the compile neither used nor searched (Bazel unused_inputs_list). |
--capabilities | Print compiler capabilities as JSON. |
--persistent_worker | Run as a Bazel persistent worker, singleplex or multiplex. |
--classpath-cache | Worker only: reuse classpath state across compiles. ELIDE_ turns it off. |
--abi-only | Write only an ABI jar, to --emit-abi, --jar or -d, without compiling bodies. |
--emit-abi PATH | Also write an ABI jar (or directory) next to the full compile. |
elide javac --jar target/app.jar -- src/Main.java src/Util.java
elide javac --jar target/app.jar --manifest src/MANIFEST.MF -- src/Main.javaABI jars
elide javac --abi-only --emit-abi header.jar -- src/Main.java src/Util.java
elide javac --emit-abi header.jar -- -d out src/Main.java src/Util.javaAn ABI (header) jar holds the public, protected and package-private signatures, constants and annotations that dependents compile against. It contains no implementation bodies and is a compile-time input, not a runnable JAR. Private implementation details are omitted. A body-only edit leaves the header byte-identical unless an annotation processor changes the generated API.
--abi-only compiles headers directly from source without checking method bodies. --emit-abi runs a header compile
alongside the full javac compile; the full compile still checks bodies and produces the runnable classes. Use separate
output paths for implementation and header output. A .jar destination writes an archive; another destination writes
class files to a directory.
For a library and its consumer:
elide javac --emit-abi lib-abi.jar -- -d lib-classes src/library/Api.java
elide javac -- -cp lib-abi.jar -d app-classes src/app/Main.java
elide java -- -cp app-classes:lib-classes app.MainThe consumer compiles against lib-abi.jar and runs against lib-classes. Classpath examples use : on macOS/Linux;
use ; on Windows. Both JAR and class-directory dependency inputs work. Pass the dependency classpath explicitly with
-cp/--class-path, or set CLASSPATH; the header compiler does not use the current directory as an implicit classpath.
--release, -source and -target set the header’s language/class-file level; the default is Java 25. Header compilation
resolves platform types against the bundled JDK, including when --release names an older JDK. It does not validate
that an API exists in that older release. Run the full javac compile with --release to enforce that API boundary and
check method bodies.
Annotation processing in headers
Explicitly enabled annotation processors run during header compilation, so their generated types can be included.
Use -processor to name processors, or -processorpath/--processor-path to discover them. With -proc:full and no
processor path, discovery uses the classpath. -proc:none disables processing.
elide javac --abi-only --emit-abi lib-abi.jar -- \
-cp annotations.jar --processor-path processor.jar -processor example.GenerateApi \
src/library/Api.javaThe processor path must include the processor’s dependencies. Header processing presents declarations without
implementation bodies; processors that inspect javac implementation details or method bodies require separate
compatibility checks. --emit-abi runs processors independently in the header and full compiles, so a processor may
execute twice. Java annotation processing support also depends on the distribution: inspect elide javac --capabilities
before wiring a processor-dependent build.
Build projects
elide build compiles src/main/java and src/test/java, or the source sets that
elide.pkl declares. To compile only main:
elide build compile-java-mainjvm {
target = 25
java {
release = 25
compiler {
mode = "embedded"
flags { "-Xlint:all" }
}
}
features {
graalvm = false
}
}| Property | Meaning | Default |
|---|---|---|
jvm.target | Bytecode target; the --release level when java.release is unset. | "auto" (21) |
jvm.java.release | --release level: compile against that JDK’s API only. | "auto" |
jvm.java.source | Use --source/--target instead of --release; ignored when release is set. | "auto" |
jvm.java.compiler.mode | "embedded" uses Elide’s javac; "external" runs the one in jvm.javaHome. | "embedded" |
jvm.java.compiler.flags | Extra javac flags; --release, -source or -target here replaces Elide’s. | none |
jvm. | --release level for that source set alone. | "auto" (the project’s) |
jvm. | javac flags for that source set, after compiler.flags. | none |
jvm.javaHome | Java home for external compiles and forked JVMs. | Elide’s own |
jvm.debug | Compile with -g. | true |
jvm.features.graalvm | Put org.graalvm.nativeimage, word and collections on the compile classpath. | false |
jvm. | Put JARs without a module descriptor on the module path as automatic modules. | true |
A source set that needs different settings, such as one compiled against java.base alone, names them under
sourceSets. Naming a source set the project does not have fails the build.
sources {
["desktopless"] { paths { "src/desktopless/java" } }
}
jvm {
java {
sourceSets {
["desktopless"] {
flags { "--limit-modules"; "java.base" }
}
}
}
}Each compile writes its arguments to javac.args beside its output. Annotation processors: Maven Dependencies.
C header bindings
From 1.6.2, embedded javac can generate Java FFM bindings from C headers and compile them alongside application sources. Generated Java stays in memory unless source export is requested. For separate source generation, filtering options and native library loading, see jextract.
For example, put this declaration in native/include/example.h:
struct Point { int x; };
int sum(int a, int b);Save the application as src/main/java/NativeClient.java; it can import generated types in the same compile:
import example.ffi.NativeApi;
import example.ffi.Point;
public class NativeClient {
public long pointSize() { return Point.sizeof(); }
public int sum(int a, int b) { return NativeApi.sum(a, b); }
}Generating and compiling these bindings needs the header, not a built implementation of sum. Calling sum at runtime
needs the native library and native access enabled. C compiler describes the separate C compilation
surface.
Declare named groups in elide.pkl and select each group in its owning Java source set:
jvm {
java {
release = 25
bindings {
["example"] {
headers { "native/include/**/*.h" }
packageName = "example.ffi"
headerClassName = "NativeApi"
includeDirs { "native/include" }
libraries { "example" }
}
}
sourceSets {
["main"] { bindings { "example" } }
}
}
}headers, packageName, and headerClassName are required. includeDirs, defines, and libraries
default to empty lists; useSystemLoadLibrary defaults to false. Paths belong to the declaring
project, including workspace members. Globs expand in sorted order and must match a file.
A group has one owning source set; tests and other source sets inherit its classes through dependsOn.
Declarations alone do not select groups.
elide build
elide javac -- --bindings example -d out src/main/java/NativeClient.java
elide javac -- --jextract 'native/include/*.h' \
--jextract-package example.ffi --jextract-class NativeApi \
--jextract-include-dir native/include --jextract-library example \
-d out src/main/java/NativeClient.javaRepeat --bindings to combine named groups. Repeat --jextract, --jextract-include-dir,
--jextract-define, and --jextract-library to add headers, search directories, preprocessor
definitions, and library names to an ad hoc group. --jextract-use-system-load-library selects
System.loadLibrary-based loading for that ad hoc group; the corresponding Pkl property is useSystemLoadLibrary.
Named and ad hoc groups can be combined; ad hoc flags do not alter named groups. These options also work inside javac
argument files.
Quote globs so Elide expands them. Ad hoc javac paths resolve against the working directory.
Builds accept the same options using --option=value, for example --bindings=example.
Add --bindings-source-set=<name> to choose the source set receiving
CLI additions; it can be omitted only when there is one production source set. Build CLI paths resolve
against the selected project root, including when using --project.
elide build --bindings=example --bindings-source-set=main
elide build --jextract='native/include/*.h' --jextract-package=example.ffi \
--jextract-class=NativeApi --jextract-include-dir=native/include \
--jextract-library=example --bindings-source-set=mainA bindings-only Java source set is valid. Keep an owning source set’s selections in Pkl for normal builds; CLI selections
add to the configured groups for that invocation. Unknown groups, unmatched globs and conflicting generated type names
fail before application compilation. Generated Java participates in normal javac annotation processing, and compiling it
with -proc:none still works.
To export sources for an IDE:
elide build --emit-bindings
elide build bindings-main --emit-bindings
elide javac -- --bindings example --emit-bindings generated-bindings -d out src/main/java/NativeClient.javaThe bindings-main target generates and exports bindings without compiling the application. Build
exports live under the project’s build directory at jvm/generated-sources/<source-set>/jextract/<group>/.
IntelliJ export marks emitted roots as generated sources, preserving each source set’s test status.
Source JARs include the generated Java. Successful exports remove stale owned files and preserve
unrelated files; failed generation retains the previous export. A direct javac export uses
<directory>/<group>/…, with adhoc as the group name for CLI header selections. Exported sources are for IDEs and
source artifacts; Elide compiles the in-memory sources rather than reading that export back in.
Each build prepares bindings again before deciding whether Java compilation is up to date. Changes in transitive headers or preprocessor results therefore affect compilation even when the top-level header has not changed. Unchanged generated sources allow the Java compile to remain cached.
Bindings require Java 23 or later, embedded javac, and a build with native tools enabled. Classpath projects and single-module compilation are supported; external javac and multi-module source-path compilation are rejected. The owning source set must contain no Kotlin: put bindings in a separate Java source set and depend on it from Kotlin. Elide does not raise the configured Java release, package native application libraries, or enable runtime native access automatically.
Bazel persistent workers
elide javac --persistent_worker and elide kotlinc --persistent_worker keep one compiler warm across Bazel actions.
Rules, setup and a worked example: Bazel.
Samples
Start from a template:
elide init --template java # Java app with a JUnit test