Project setup
A StoatFlow application has non-trivial build requirements: a JDK 25 toolchain, two mandatory JVM flags at compile, test, and run time, a fat JAR with the right Main-Class, and — when you opt in — a container-aware heap entrypoint and a GraalVM native image with the correct metadata. You don't hand-maintain any of that. StoatFlow ships first-class build conventions for both Gradle and Maven, kept deliberately symmetric, so a production-ready build is a few lines of config.
Apply the conventions
Register the StoatFlow repository (and, for Gradle, the plugin-marker repository) as shown in Installation. Then apply the convention:
// build.gradle.kts
plugins {
kotlin("jvm") version "2.4.0" // omit for a Java-only project
id("io.stoatflow") version "<stoatflow-version>"
}
stoatflow {
mainClass.set("com.example.MainKt") // your application entry point
}
dependencies {
implementation("io.stoatflow:stoatflow-runtime:<stoatflow-version>")
}
<!-- pom.xml -->
<parent>
<groupId>io.stoatflow</groupId>
<artifactId>stoatflow-parent</artifactId>
<version>REPLACE-WITH-CURRENT-RELEASE</version> <!-- literal — Maven can't resolve a property in <parent> -->
<relativePath/>
</parent>
<properties>
<stoatflow.mainClass>com.example.Main</stoatflow.mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.stoatflow</groupId>
<artifactId>stoatflow-runtime</artifactId>
<!-- version managed by the BOM the parent imports -->
</dependency>
</dependencies>
The current published version is 1.0.0-alpha.16 — substitute it for the placeholders above (the Maven <parent> version must be a literal; Maven does not interpolate properties in parent coordinates). For a Kotlin fun main() in Main.kt the entry point is the synthetic MainKt class (com.example.MainKt); for a Java public static void main it's the class itself (com.example.Main).
stoatflow-bom for you, so you omit the <version> on every io.stoatflow:* dependency. If you already have a corporate parent POM, import the BOM directly instead and set the toolchain by hand — see Maven reference → Using just the BOM.What you get
Applying the convention — with no further configuration — already gives you all of this:
| Capability | What it does |
|---|---|
| JDK 25 toolchain | Compiles and runs on JDK 25 — required for the virtual-thread / structured-concurrency preview features StoatFlow is built on. |
| Mandatory JVM flags | --enable-preview and --enable-native-access=ALL-UNNAMED at compile, test, and run time. Non-negotiable and always added. |
| Default GC flag | -XX:+UseG1GC — overridable, but on by default for predictable pauses even on a single core or a small container. |
| Runnable fat JAR | A shaded -all JAR with the Main-Class manifest and merged service files. java -jar just works. |
| Test conventions | JUnit Platform with the preview/native-access flags; an integrationTest task that runs only @Tag("IntegrationTest") tests (the unit run excludes them). |
| Docker image (opt-in) | A reproducible JVM container via Jib — no Dockerfile, no Docker daemon to assemble the layers. See Docker. |
| Native image (opt-in) | A GraalVM native image with all of StoatFlow's reflection / JNI / FFM metadata already registered. One line to turn on. See Native image. |
-XX:+UseG1GC is set explicitly because the JVM only auto-selects G1 on "server-class" hardware (≥ 2 CPUs and ≥ ~2 GB memory), falling back to the single-threaded Serial collector below that. Setting it explicitly gives short, predictable GC pauses even on a single core — which matters for stream processing, where a long stop-the-world pause stalls commit barriers. See Installation for the full reasoning.Configure the build
mainClass is the only property most projects set. The container and native-image builds are opt-in and default to off. Here are the common knobs — the full surface (every property, task, and default) lives in the reference pages, linked below.
// build.gradle.kts
stoatflow {
mainClass.set("com.example.MainKt")
docker {
enabled.set(true) // opt in to the Jib image
imageName.set("acme/word-count")
}
nativeImage {
enabled.set(true) // opt in to the GraalVM native image
gc.set("G1") // Oracle GraalVM; omit for Serial (stateless only)
}
}
<!-- pom.xml — container/native config lives in <properties> -->
<properties>
<stoatflow.mainClass>com.example.Main</stoatflow.mainClass>
<stoatflow.docker.imageName>acme/word-count</stoatflow.docker.imageName>
<stoatflow.nativeImage.gc>G1</stoatflow.nativeImage.gc>
</properties>
-P, not <properties>. Setting <stoatflow.docker.enabled> does not turn the build on — Maven <activation> reads system/user properties, not your project's. Activate the build with mvn -Pstoatflow-docker … / mvn -Pstoatflow-native … (the build commands below). The <properties> above only configure the build once a profile is active.- Full Gradle DSL — every
stoatflow { }property, task, and default: Gradle plugin reference. - Full Maven surface — the three artifacts, every
<properties>key, and the four goals: Maven reference.
Gradle ⇄ Maven: the same conventions, both tools
Everything one tool does has a counterpart on the other. If you've configured one, this is the map to the other:
| Concept | Gradle (io.stoatflow plugin) | Maven (stoatflow-parent + plugin) |
|---|---|---|
| Apply the conventions | plugins { id("io.stoatflow") } | <parent>io.stoatflow:stoatflow-parent</parent> |
| Entry point | stoatflow { mainClass.set("…") } | <stoatflow.mainClass>…</stoatflow.mainClass> |
| Fat JAR | ./gradlew shadowJar (-all.jar) | mvn package (-all.jar, via shade) |
| Run (forked, preview on) | ./gradlew run | mvn stoatflow:run |
| Build a Docker image | docker { enabled.set(true) } → ./gradlew jibDockerBuild | <stoatflow.docker…> → mvn -Pstoatflow-docker package |
| Build a native image | nativeImage { enabled.set(true) } → ./gradlew nativeDockerBuild | <stoatflow.nativeImage…> → mvn stoatflow:native-docker-build |
| Aligned dependency versions | resolved transitively from stoatflow-runtime | import stoatflow-bom (the parent does this for you) |
Dockerfile.native, and the same container entrypoint scripts (shared from one internal module). A change to the native flags or the heap model lands on both at once — they can't drift.Shared build behaviours
These are identical across Gradle and Maven — worth understanding once.
- Reproducible images. By default the image
creationTime(JVM/Jib) andSOURCE_DATE_EPOCH(native) are pinned to your HEAD commit timestamp, not wall-clock — so identical source produces an identical digest with a meaningfulCreateddate. Outside a git repo the timestamp falls back to the Unix epoch. Turn it off withreproducibleBuild = falseto restore wall-clock. - Container-aware heap sizing. The bundled entrypoint sizes the JVM (or native) heap from the container's memory limit (cgroup v1/v2), not the host's — no
-Xmxguesswork under Kubernetes limits. The full model lives in Docker → Container-aware heap sizing. - RocksDB off-heap auto-detection. If
org.rocksdbis on the runtime classpath (it is, viastoatflow-runtime/stoatflow-core), the image setsSTOATFLOW_ROCKSDB_MB=256so the entrypoint reserves off-heap memory for the state store before sizing the heap. Override the reservation explicitly, or set it to0to skip the reservation for an app that only uses in-memory stores. Automatic on both build tools.
Build and run
./gradlew run # run locally with the required JVM flags
./gradlew shadowJar # build the fat JAR → build/libs/<name>-<version>-all.jar
./gradlew test # unit tests (IntegrationTest tag excluded)
./gradlew integrationTest # integration tests (only the IntegrationTest tag)
./gradlew jibDockerBuild # build the container image (docker.enabled)
./gradlew nativeDockerBuild # build the native image (nativeImage.enabled)
mvn stoatflow:run # run locally (forked JVM, --enable-preview)
mvn package # fat JAR → target/<name>-<version>-all.jar
mvn test # unit tests (IntegrationTest tag excluded)
mvn verify # + integration tests (failsafe)
mvn -Pstoatflow-docker package # build the container image (one command)
mvn stoatflow:native-docker-build # build the native image (run mvn package first)
mvn stoatflow:… short prefix. To call the plugin's standalone goals by their short prefix, add io.stoatflow to <pluginGroups> in your ~/.m2/settings.xml. Without it, use full coordinates — mvn io.stoatflow:stoatflow-maven-plugin:run. See the Maven reference.Worked examples
A complete build for each tool, both languages:
// build.gradle.kts
plugins {
kotlin("jvm") version "2.4.0" // drop this line for a Java-only project
id("io.stoatflow") version "<stoatflow-version>"
}
dependencies {
implementation("io.stoatflow:stoatflow-runtime:<stoatflow-version>")
testImplementation("io.stoatflow:stoatflow-test-utils:<stoatflow-version>")
}
stoatflow {
mainClass.set("com.example.MainKt") // Java: "com.example.Main"
docker {
enabled.set(true)
imageName.set("acme/word-count")
}
}
<!-- pom.xml — a StoatFlow app on the parent POM -->
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.stoatflow</groupId>
<artifactId>stoatflow-parent</artifactId>
<version>REPLACE-WITH-CURRENT-RELEASE</version> <!-- literal — Maven can't resolve a property in <parent> -->
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>word-count</artifactId>
<version>1.0.0</version>
<properties>
<stoatflow.mainClass>com.example.MainKt</stoatflow.mainClass> <!-- Java: com.example.Main -->
<stoatflow.docker.imageName>acme/word-count</stoatflow.docker.imageName>
</properties>
<!-- Kotlin only: point Maven at src/main/kotlin and add the stdlib -->
<build><sourceDirectory>src/main/kotlin</sourceDirectory></build>
<dependencies>
<dependency>
<groupId>io.stoatflow</groupId>
<artifactId>stoatflow-runtime</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version> <!-- omit for a Java app -->
</dependency>
</dependencies>
</project>
Next steps
- Your first app — build and run a complete word-count app on the runtime.
- Docker and Native image — the full container and GraalVM build guides.
- Gradle plugin reference · Maven reference — every knob, task, and default.
- Stuck on the build? Get in touch — real people read every email during the alpha.
Your first app
Build and run a complete word-count stream processor on the StoatFlow runtime — in Kotlin or Java.
Core concepts
A map of the StoatFlow concept pages and the order to read them in — from the single-instance architecture through exactly-once, lanes, state, event time, configuration, and error handling.