feat(java): foundation — JNI crate, layered SDK, producer/inspection#299
Conversation
jni-rs (cdylib) shell over taskito-core, peer to the node/python shells. Producer surface (open/enqueue/get/cancel/stats) with a panic-safe FFI boundary.
Taskito factory -> Queue interface (pkg-private impl), typed Task<T>, JobStatus enum, Serializer SPI, and a QueueBackend seam decoupling the API from the JNI layer.
Toolchain 11, cargo native build + classifier staging, Spotless (palantir) and Checkstyle. Wrapper pinned to 8.12.
Add inspection/admin/log entry points (enqueueMany, listJobs, metrics,
DLQ, settings, logs, ...) and split queue.rs into queue/{mod,inspect,
admin,logs}.rs as the surface grew.
Typed methods over the new surface plus DeadJob/JobError/TaskMetric/ Worker/TaskLog/JobFilter models; the QueueBackend SPI mirrors the native ops.
|
Warning Review limit reached
More reviews will be available in 22 minutes and 18 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
📝 WalkthroughWalkthroughThis PR adds the Java Taskito SDK, JNI bridge, and supporting Rust bindings. It defines queue/job models and public APIs, wires native queue operations through JNI, and adds Gradle build, wrapper, documentation, and integration tests. ChangesTaskito Java SDK and JNI bindings
Sequence Diagram(s)sequenceDiagram
participant TaskitoBuilder
participant JniQueueBackend
participant NativeQueue
participant RustOpen
participant BackendOpen
TaskitoBuilder->>JniQueueBackend: open(optionsJson)
JniQueueBackend->>NativeQueue: open(optionsJson)
NativeQueue->>RustOpen: JNI call
RustOpen->>BackendOpen: parse OpenOptions
BackendOpen-->>RustOpen: QueueHandle
RustOpen-->>NativeQueue: handle
NativeQueue-->>JniQueueBackend: handle
JniQueueBackend-->>TaskitoBuilder: QueueBackend
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (1)
sdks/java/src/main/java/org/byteveda/taskito/DefaultQueue.java (1)
19-19: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winMake the views mapper tolerant of unknown fields for forward compatibility.
VIEWSuses Jackson defaults, whereFAIL_ON_UNKNOWN_PROPERTIESis enabled. Any new field the native layer adds to a JSON view (jobs, stats, metrics, etc.) will then causedecode/decodeList/decodeMapto throw, breaking older SDK clients against newer cores. Since these decode native-produced views, prefer disabling that feature.♻️ Proposed change
- private static final ObjectMapper VIEWS = new ObjectMapper(); + private static final ObjectMapper VIEWS = + new ObjectMapper() + .disable(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sdks/java/src/main/java/org/byteveda/taskito/DefaultQueue.java` at line 19, The VIEWS ObjectMapper in DefaultQueue currently uses Jackson defaults, so decode/decodeList/decodeMap will fail on forward-added fields from native-produced views. Update the VIEWS mapper initialization to disable FAIL_ON_UNKNOWN_PROPERTIES so jobs/stats/metrics JSON remains backward-compatible with newer core payloads. Use the existing VIEWS symbol in DefaultQueue and keep the change localized to the mapper configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/taskito-java/src/convert.rs`:
- Around line 59-66: The scheduled_at calculation in NewJob construction can
overflow when adding delay_ms to now_millis(). Update the delay handling in
convert.rs so the logic around delay, now_millis(), and the NewJob initializer
uses checked or saturating arithmetic before assigning scheduled_at, and keep
the existing clamping of negative delays to zero.
In `@crates/taskito-java/src/ffi.rs`:
- Around line 69-74: The array loops in read_bytes_array and new_string_array
are leaking JNI local references on each iteration because
get_object_array_element and new_string create per-element refs that are never
released. Fix this by wrapping each loop body in a local frame or otherwise
explicitly deleting/auto-releasing the per-iteration local ref before the next
iteration, so large byte[][] and String[] inputs do not exhaust the local-ref
table. Use the existing read_bytes_array and new_string_array functions as the
places to apply the cleanup.
In `@crates/taskito-java/src/handle.rs`:
- Around line 13-29: The native handle helpers in borrow and drop_handle are
being used without any Java-side lifecycle guard, so calls into DefaultQueue and
JniQueueBackend can race with close and double-release the same handle. Add a
closed-state flag and synchronize lifecycle-sensitive methods so enqueue and
inspection methods refuse work after close, and ensure close only releases the
native pointer once by guarding the path that ultimately calls drop_handle.
In `@crates/taskito-java/src/queue/mod.rs`:
- Around line 49-56: `Java_org_byteveda_taskito_internal_NativeQueue_close` is
the only JNI entry point in this module that bypasses the panic-safety wrapper.
Update it to use the same `guard(&mut env, (), |_env| { ...; Ok(()) })` pattern
used by the other JNI methods, and keep the `drop_handle::<QueueHandle>(handle)`
call inside that closure so any panic from destruction is contained before
crossing JNI.
In `@sdks/java/build.gradle.kts`:
- Around line 65-74: The native staging in copyNative only packages the library
for the build machine’s platform, which makes the JAR non-portable at runtime.
Update the sdks/java build logic around copyNative, nativeStaging, and
platformClassifier() so the published JAR includes JNI binaries for all
supported OS/arch targets or uses a packaging/loading strategy that matches the
runtime platform instead of only the current one. Ensure the resource layout and
processResources flow still point native loading code to the correct
platform-specific library without requiring a manual override path.
In `@sdks/java/gradle/wrapper/gradle-wrapper.properties`:
- Around line 1-5: The Gradle wrapper configuration is still pointing to 8.12
and is missing the checksum. Update the wrapper settings in
gradle-wrapper.properties to use Gradle 8.12.1, and add the corresponding
distributionSha256Sum so the wrapper is pinned and verified. Use the existing
distributionUrl entry and the wrapper properties keys in this file to make the
change.
In `@sdks/java/README.md`:
- Around line 29-39: The package structure tree in the Java README is a fenced
code block without a language tag, which triggers markdownlint MD040. Update the
fenced block near the org.byteveda.taskito overview to include an appropriate
language identifier for the tree/listing, keeping the content the same and
preserving the existing Taskito/Queue/serialization/spi/internal structure.
In `@sdks/java/src/main/java/org/byteveda/taskito/EnqueueOptions.java`:
- Around line 57-69: Reject invalid negative enqueue limits/timeouts in
EnqueueOptions.Builder by validating inputs in maxRetries, timeoutMs, and
delayMs before assigning them; if a value is below zero, throw an appropriate
exception so invalid max_retries/timeout_ms settings cannot reach native job
construction. Keep the fix localized to the builder methods in EnqueueOptions
and preserve the existing fluent API for valid values.
In `@sdks/java/src/main/java/org/byteveda/taskito/internal/JniQueueBackend.java`:
- Around line 164-167: Make JniQueueBackend.close() idempotent by preventing
NativeQueue.close(handle) from being called more than once on the same instance.
Update the close() method to either guard with a closed flag or clear the handle
after the first successful close so subsequent calls become no-ops; use the
JniQueueBackend and NativeQueue symbols to locate the code.
In `@sdks/java/src/main/java/org/byteveda/taskito/JobStatus.java`:
- Around line 21-24: Make JobStatus.fromWire null-safe and improve the
unknown-value failure path: guard the input before calling
toUpperCase(Locale.ROOT), and replace the raw valueOf exception with a clearer
error that includes the offending wire value. Keep the fix localized to fromWire
in JobStatus so deserialization fails predictably for null or unrecognized
statuses.
In `@sdks/java/src/main/java/org/byteveda/taskito/spi/QueueBackend.java`:
- Line 19: Clarify the SPI contract for QueueBackend.enqueueMany: the current
optionsJson parameter is ambiguous, so update the method signature/documentation
to state that it must be a JSON array of per-job EnqueueOptions matching
payloads length, unlike enqueue's single options object. If you change the name
to perJobOptionsJson, keep QueueBackend.enqueueMany and any
callers/implementations aligned so the intent is obvious everywhere.
In `@sdks/java/src/main/java/org/byteveda/taskito/Task.java`:
- Around line 9-22: Validate Task inputs eagerly in the Task constructor and the
factory/helpers so invalid values fail fast instead of later in
serialization/JNI paths. Add required checks in Task.of and Task.withOptions,
and/or centralize them in the private Task constructor, for non-null payloadType
and options plus non-null/non-blank name; keep the behavior consistent for all
creation paths by referencing Task.of and Task.withOptions.
---
Nitpick comments:
In `@sdks/java/src/main/java/org/byteveda/taskito/DefaultQueue.java`:
- Line 19: The VIEWS ObjectMapper in DefaultQueue currently uses Jackson
defaults, so decode/decodeList/decodeMap will fail on forward-added fields from
native-produced views. Update the VIEWS mapper initialization to disable
FAIL_ON_UNKNOWN_PROPERTIES so jobs/stats/metrics JSON remains
backward-compatible with newer core payloads. Use the existing VIEWS symbol in
DefaultQueue and keep the change localized to the mapper configuration.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: fcd0a615-8284-41f5-97b0-ba19cf4ee3e9
⛔ Files ignored due to path filters (2)
Cargo.lockis excluded by!**/*.locksdks/java/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jar
📒 Files selected for processing (47)
Cargo.tomlcrates/taskito-java/Cargo.tomlcrates/taskito-java/src/backend.rscrates/taskito-java/src/convert.rscrates/taskito-java/src/error.rscrates/taskito-java/src/ffi.rscrates/taskito-java/src/handle.rscrates/taskito-java/src/jvm.rscrates/taskito-java/src/lib.rscrates/taskito-java/src/queue/admin.rscrates/taskito-java/src/queue/inspect.rscrates/taskito-java/src/queue/logs.rscrates/taskito-java/src/queue/mod.rssdks/java/.gitignoresdks/java/README.mdsdks/java/build.gradle.ktssdks/java/config/checkstyle/checkstyle.xmlsdks/java/gradle/wrapper/gradle-wrapper.propertiessdks/java/gradlewsdks/java/gradlew.batsdks/java/settings.gradle.ktssdks/java/src/main/java/org/byteveda/taskito/DeadJob.javasdks/java/src/main/java/org/byteveda/taskito/DefaultQueue.javasdks/java/src/main/java/org/byteveda/taskito/EnqueueOptions.javasdks/java/src/main/java/org/byteveda/taskito/Job.javasdks/java/src/main/java/org/byteveda/taskito/JobError.javasdks/java/src/main/java/org/byteveda/taskito/JobFilter.javasdks/java/src/main/java/org/byteveda/taskito/JobStatus.javasdks/java/src/main/java/org/byteveda/taskito/Queue.javasdks/java/src/main/java/org/byteveda/taskito/QueueStats.javasdks/java/src/main/java/org/byteveda/taskito/Task.javasdks/java/src/main/java/org/byteveda/taskito/TaskLog.javasdks/java/src/main/java/org/byteveda/taskito/TaskMetric.javasdks/java/src/main/java/org/byteveda/taskito/Taskito.javasdks/java/src/main/java/org/byteveda/taskito/TaskitoException.javasdks/java/src/main/java/org/byteveda/taskito/Worker.javasdks/java/src/main/java/org/byteveda/taskito/internal/JniQueueBackend.javasdks/java/src/main/java/org/byteveda/taskito/internal/NativeLoader.javasdks/java/src/main/java/org/byteveda/taskito/internal/NativeQueue.javasdks/java/src/main/java/org/byteveda/taskito/internal/package-info.javasdks/java/src/main/java/org/byteveda/taskito/package-info.javasdks/java/src/main/java/org/byteveda/taskito/serialization/JsonSerializer.javasdks/java/src/main/java/org/byteveda/taskito/serialization/Serializer.javasdks/java/src/main/java/org/byteveda/taskito/serialization/package-info.javasdks/java/src/main/java/org/byteveda/taskito/spi/QueueBackend.javasdks/java/src/main/java/org/byteveda/taskito/spi/package-info.javasdks/java/src/test/java/org/byteveda/taskito/QueueTest.java
First of 9 sequential PRs adding a Java SDK for Taskito — a hand-written JNI binding over the Rust core. This is the foundation the rest build on.
What's here
crates/taskito-java— ajni-rsbinding crate (cdylib) with a panic-safe FFI boundary (Rust panics become aTaskitoExceptionand never unwind across FFI), opaque handle marshalling, and core-error translation.sdks/java):Taskitofactory →Queueinterface → package-privateDefaultQueue; aspi.QueueBackendseam that decouples the public API from JNI (mockable);SerializerSPI with aJsonSerializerdefault; typedTask<T>and aJobStatusenum.Scope
Producer and read APIs only. Workers, workflows, serializers/dashboard, locks, and the rest follow in later PRs. Targets Java 11.
Verification
cargo clippy(taskito-java) is clean and the Java sources compile under--release 11. The Rust workspace is covered by the existing Rust CI; a dedicated Java CI job arrives in a later PR in this series.Summary by CodeRabbit
New Features
Bug Fixes
Documentation