diff --git a/.github/actions/setup-java/action.yml b/.github/actions/setup-java/action.yml
index 763878bc..01c3561e 100644
--- a/.github/actions/setup-java/action.yml
+++ b/.github/actions/setup-java/action.yml
@@ -2,8 +2,8 @@ name: Set up Java
description: >-
Installs a Temurin JDK and enables Gradle build caching. Used by every
Java-touching job so toolchain setup lives in one place. The SDK targets Java
- 17 bytecode via `--release 17`, so any JDK >= 17 works; we use 21 to match the
- publish workflow.
+ 17 bytecode via `--release 17`, so any JDK >= 17 works; the publish workflow
+ builds on 25 so the FFM overlay ships.
inputs:
java-version:
diff --git a/.github/workflows/publish-java.yml b/.github/workflows/publish-java.yml
index 621117e0..3a391cf7 100644
--- a/.github/workflows/publish-java.yml
+++ b/.github/workflows/publish-java.yml
@@ -150,11 +150,30 @@ jobs:
# The cargo/copyNative tasks build only the host platform; skip them since
# every platform's binary is already staged in resources from the matrix.
- # Dry-run only assembles the JAR — keep publishing secrets out of it.
- - name: Assemble JAR (dry-run)
+ # Dry-run assembles all publish artifacts so javadoc/doclint failures
+ # surface here, not during the live publish; secrets stay out of it.
+ - name: Assemble publish artifacts (dry-run)
if: inputs.dry-run
working-directory: sdks/java
- run: ./gradlew jar -x cargoBuild -x copyNative --no-daemon
+ run: ./gradlew jar sourcesJar plainJavadocJar -x cargoBuild -x copyNative --no-daemon
+
+ # MR-jar layout + native bundling only come together here — assert them.
+ - name: Verify jar contents (dry-run)
+ if: inputs.dry-run
+ working-directory: sdks/java
+ run: |
+ jar=$(ls build/libs/taskito-*[0-9].jar)
+ echo "checking $jar"
+ unzip -p "$jar" META-INF/MANIFEST.MF | grep -q "Multi-Release: true"
+ unzip -l "$jar" | grep -q "META-INF/versions/22/org/byteveda/taskito/internal/FfmTransport.class"
+ for platform in linux-x86_64 linux-aarch64 osx-x86_64 osx-aarch64 windows-x86_64; do
+ unzip -l "$jar" | grep -q "org/byteveda/taskito/native/$platform/" \
+ || { echo "missing native library for $platform"; exit 1; }
+ done
+ sources=$(ls build/libs/taskito-*-sources.jar)
+ if unzip -l "$sources" | grep -qE "org/byteveda/taskito/(native|dashboard)/"; then
+ echo "sources jar must not bundle natives or dashboard assets"; exit 1
+ fi
- name: Publish to Maven Central
if: ${{ !inputs.dry-run }}
diff --git a/Cargo.toml b/Cargo.toml
index a32eeef1..2d825423 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,3 +22,8 @@ openssl-sys = { version = "0.9", features = ["vendored"] }
pyo3 = { version = "0.29", features = ["multiple-pymethods"] }
async-trait = "0.1"
dagron-core = { git = "https://github.com/ByteVeda/dagron.git", rev = "d1b61aaf2ed2d516b9a239f089a55b143cb05f65" }
+
+# Release cdylibs ship inside SDK packages; strip + thin-LTO keep them small.
+[profile.release]
+strip = true
+lto = "thin"
diff --git a/sdks/java/README.md b/sdks/java/README.md
index f03be585..c1f26682 100644
--- a/sdks/java/README.md
+++ b/sdks/java/README.md
@@ -3,16 +3,53 @@
A typed Java 17+ client over the Taskito Rust core, via a hand-written JNI shell
(`crates/taskito-java`).
-> Status: **build-out**. Producer + inspection + admin + logs, worker task
-> execution, middleware, JSON/signed/encrypted/MessagePack serializers,
-> dashboard, webhooks, CLI, distributed locks, periodic/cron, and the full
-> workflow engine (DAG, fan-out/fan-in, gates, conditions, sub-workflows, sagas,
-> analysis + visualization, canvas) are implemented and verified end-to-end.
-> Also: worker resources (DI), enqueue predicates, a KEDA scaler endpoint,
-> producer batching, in-process autoscaling, observability middleware
-> (Micrometer Observation + Sentry), and a Spring Boot 3 starter. Baseline:
-> **Java 17** (`--release 17`). Spring Boot 3 apps can adopt it directly;
-> native `.so` is JDK-independent.
+Feature-complete: producer + inspection + admin + logs, worker task execution,
+middleware, JSON/signed/encrypted/MessagePack serializers, dashboard, webhooks,
+CLI, distributed locks, periodic/cron, and the full workflow engine (DAG,
+fan-out/fan-in, gates, conditions, sub-workflows, sagas, analysis +
+visualization, canvas). Also: worker resources (DI), enqueue predicates, a KEDA
+scaler endpoint, producer batching, in-process autoscaling, observability
+middleware (Micrometer Observation + Sentry), and a Spring Boot 3 starter.
+Baseline: **Java 17** (`--release 17`); on JDK 22+ hot byte ops take a Panama
+(FFM) fast path automatically. The native library is bundled per platform —
+no separate install.
+
+## Install
+
+```kotlin
+// Gradle
+implementation("org.byteveda:taskito:0.18.0")
+annotationProcessor("org.byteveda:taskito-processor:0.18.0") // compile-time TaskHandler bindings
+```
+
+```xml
+
+
+ org.byteveda
+ taskito
+ 0.18.0
+
+```
+
+```xml
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.byteveda
+ taskito-processor
+ 0.18.0
+
+
+
+
+```
+
+Companion artifacts: `org.byteveda:taskito-test` (in-memory backend for unit
+tests) and `org.byteveda:taskito-spring` (Boot 3 starter).
## Migration
diff --git a/sdks/java/build.gradle.kts b/sdks/java/build.gradle.kts
index f22c5762..02992fdb 100644
--- a/sdks/java/build.gradle.kts
+++ b/sdks/java/build.gradle.kts
@@ -135,6 +135,10 @@ tasks.named("processResources") { dependsOn(copyNative) }
// resource srcDirs, so any Jar task reads the staged native dir. Wire the
// dependency lazily — sourcesJar is registered after this script evaluates.
tasks.withType().configureEach { dependsOn(copyNative) }
+// Sources jar ships sources only — cdylibs + dashboard already ship in the main jar.
+tasks.withType().matching { it.name == "sourcesJar" }.configureEach {
+ exclude("org/byteveda/taskito/native/**", "org/byteveda/taskito/dashboard/**")
+}
// --- FFM fast-path overlay (Multi-Release JAR) ----------------------------
// Base classes target 17 (JNI transport + the fallback). On a build JDK >= 22 we