Skip to content

feat(java): foundation — JNI crate, layered SDK, producer/inspection#299

Merged
pratyush618 merged 16 commits into
masterfrom
feat/java-foundation
Jun 25, 2026
Merged

feat(java): foundation — JNI crate, layered SDK, producer/inspection#299
pratyush618 merged 16 commits into
masterfrom
feat/java-foundation

Conversation

@pratyush618

@pratyush618 pratyush618 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

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 — a jni-rs binding crate (cdylib) with a panic-safe FFI boundary (Rust panics become a TaskitoException and never unwind across FFI), opaque handle marshalling, and core-error translation.
  • Layered Java API (sdks/java): Taskito factory → Queue interface → package-private DefaultQueue; a spi.QueueBackend seam that decouples the public API from JNI (mockable); Serializer SPI with a JsonSerializer default; typed Task<T> and a JobStatus enum.
  • Producer / inspection / admin / logs surface (~25 native functions): enqueue / enqueueMany, getJob / getResult, cancel / requestCancel, stats / listJobs / metrics, dead-letter ops, pause / resume, settings, and task logs.
  • Build tooling: Gradle (Kotlin DSL) with Spotless + Checkstyle, plus a README.

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

    • Added a Java SDK with typed queue operations, job models, queue stats, metrics, dead-job handling, settings, and task logs.
    • Added support for opening queues from Java and using native backend access under the hood.
    • Added JSON-based enqueue options and payload/result serialization.
  • Bug Fixes

    • Improved native error handling so failures are surfaced as Java exceptions.
    • Added safer handling for invalid inputs and native panics.
  • Documentation

    • Added package and README guidance, plus build and usage setup for the Java SDK.

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.
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@pratyush618, we couldn't start this review because you've reached your PR review rate limit.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5cf8c7f3-e06e-4eb0-9665-4c4416f75032

📥 Commits

Reviewing files that changed from the base of the PR and between 186a5cd and 7a10961.

📒 Files selected for processing (10)
  • crates/taskito-java/src/convert.rs
  • crates/taskito-java/src/ffi.rs
  • crates/taskito-java/src/queue/mod.rs
  • sdks/java/README.md
  • sdks/java/gradle/wrapper/gradle-wrapper.properties
  • sdks/java/src/main/java/org/byteveda/taskito/EnqueueOptions.java
  • sdks/java/src/main/java/org/byteveda/taskito/JobStatus.java
  • sdks/java/src/main/java/org/byteveda/taskito/Task.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/JniQueueBackend.java
  • sdks/java/src/main/java/org/byteveda/taskito/spi/QueueBackend.java
📝 Walkthrough

Walkthrough

This 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.

Changes

Taskito Java SDK and JNI bindings

Layer / File(s) Summary
JNI crate scaffolding
Cargo.toml, crates/taskito-java/Cargo.toml, crates/taskito-java/src/{error,ffi,handle,jvm,lib}.rs
The Rust workspace and taskito-java crate are wired for JNI, with error translation, handle helpers, JVM caching, marshalling helpers, and JNI_OnLoad setup.
Backend selection and JSON views
crates/taskito-java/src/{backend,convert}.rs
Backend opening, enqueue option parsing, typed view conversion, status mapping, and JSON serialization helpers are added for the Rust binding layer.
NativeQueue lifecycle and enqueue paths
crates/taskito-java/src/queue/mod.rs
The Rust JNI queue entrypoints add open/close, enqueue, batch enqueue, job/result retrieval, cancellation, and progress handling.
Inspection, admin, and log JNI entrypoints
crates/taskito-java/src/queue/{inspect,admin,logs}.rs
The Rust JNI inspection, dead-job/admin, settings, and task-log entrypoints serialize storage access to JSON or primitive JNI values.
Public Java contracts and DTOs
sdks/java/src/main/java/org/byteveda/taskito/*.java, sdks/java/src/main/java/org/byteveda/taskito/{serialization,spi}/*.java, sdks/java/src/main/java/org/byteveda/taskito/package-info.java
The Java API adds queue/backend interfaces, serializer support, and immutable models for jobs, stats, logs, metrics, dead jobs, and workers.
Java native bridge and queue wiring
sdks/java/src/main/java/org/byteveda/taskito/internal/*.java, sdks/java/src/main/java/org/byteveda/taskito/DefaultQueue.java, sdks/java/src/main/java/org/byteveda/taskito/Taskito.java, sdks/java/src/main/java/org/byteveda/taskito/internal/package-info.java
The Java native layer adds library loading, JNI declarations, the backend adapter, DefaultQueue, and Taskito builder/open wiring.
Build, wrapper, docs, and tests
sdks/java/{build.gradle.kts,README.md,settings.gradle.kts,.gitignore,gradlew,gradlew.bat}, sdks/java/gradle/wrapper/gradle-wrapper.properties, sdks/java/config/checkstyle/checkstyle.xml, sdks/java/src/test/java/org/byteveda/taskito/QueueTest.java
Gradle wiring, wrapper scripts, docs, .gitignore, and queue integration tests are added for the Java SDK.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

tests

Poem

🐰 I dug a tunnel through Rust and Java bright,
JNI carrots twinkled in the moonlit night.
Jobs hopped, logs hopped, and queues said "hallo",
Now byte-sized burrows flow soft and mellow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.19% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately captures the main change: a foundational Java JNI crate and layered SDK with producer and inspection APIs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/java-foundation

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (1)
sdks/java/src/main/java/org/byteveda/taskito/DefaultQueue.java (1)

19-19: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Make the views mapper tolerant of unknown fields for forward compatibility.

VIEWS uses Jackson defaults, where FAIL_ON_UNKNOWN_PROPERTIES is enabled. Any new field the native layer adds to a JSON view (jobs, stats, metrics, etc.) will then cause decode/decodeList/decodeMap to 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

📥 Commits

Reviewing files that changed from the base of the PR and between 47dbf03 and 186a5cd.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • sdks/java/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
📒 Files selected for processing (47)
  • Cargo.toml
  • crates/taskito-java/Cargo.toml
  • crates/taskito-java/src/backend.rs
  • crates/taskito-java/src/convert.rs
  • crates/taskito-java/src/error.rs
  • crates/taskito-java/src/ffi.rs
  • crates/taskito-java/src/handle.rs
  • crates/taskito-java/src/jvm.rs
  • crates/taskito-java/src/lib.rs
  • crates/taskito-java/src/queue/admin.rs
  • crates/taskito-java/src/queue/inspect.rs
  • crates/taskito-java/src/queue/logs.rs
  • crates/taskito-java/src/queue/mod.rs
  • sdks/java/.gitignore
  • sdks/java/README.md
  • sdks/java/build.gradle.kts
  • sdks/java/config/checkstyle/checkstyle.xml
  • sdks/java/gradle/wrapper/gradle-wrapper.properties
  • sdks/java/gradlew
  • sdks/java/gradlew.bat
  • sdks/java/settings.gradle.kts
  • sdks/java/src/main/java/org/byteveda/taskito/DeadJob.java
  • sdks/java/src/main/java/org/byteveda/taskito/DefaultQueue.java
  • sdks/java/src/main/java/org/byteveda/taskito/EnqueueOptions.java
  • sdks/java/src/main/java/org/byteveda/taskito/Job.java
  • sdks/java/src/main/java/org/byteveda/taskito/JobError.java
  • sdks/java/src/main/java/org/byteveda/taskito/JobFilter.java
  • sdks/java/src/main/java/org/byteveda/taskito/JobStatus.java
  • sdks/java/src/main/java/org/byteveda/taskito/Queue.java
  • sdks/java/src/main/java/org/byteveda/taskito/QueueStats.java
  • sdks/java/src/main/java/org/byteveda/taskito/Task.java
  • sdks/java/src/main/java/org/byteveda/taskito/TaskLog.java
  • sdks/java/src/main/java/org/byteveda/taskito/TaskMetric.java
  • sdks/java/src/main/java/org/byteveda/taskito/Taskito.java
  • sdks/java/src/main/java/org/byteveda/taskito/TaskitoException.java
  • sdks/java/src/main/java/org/byteveda/taskito/Worker.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/JniQueueBackend.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/NativeLoader.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/NativeQueue.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/package-info.java
  • sdks/java/src/main/java/org/byteveda/taskito/package-info.java
  • sdks/java/src/main/java/org/byteveda/taskito/serialization/JsonSerializer.java
  • sdks/java/src/main/java/org/byteveda/taskito/serialization/Serializer.java
  • sdks/java/src/main/java/org/byteveda/taskito/serialization/package-info.java
  • sdks/java/src/main/java/org/byteveda/taskito/spi/QueueBackend.java
  • sdks/java/src/main/java/org/byteveda/taskito/spi/package-info.java
  • sdks/java/src/test/java/org/byteveda/taskito/QueueTest.java

Comment thread crates/taskito-java/src/convert.rs
Comment thread crates/taskito-java/src/ffi.rs
Comment thread crates/taskito-java/src/handle.rs
Comment thread crates/taskito-java/src/queue/mod.rs Outdated
Comment thread sdks/java/build.gradle.kts
Comment thread sdks/java/src/main/java/org/byteveda/taskito/EnqueueOptions.java
Comment thread sdks/java/src/main/java/org/byteveda/taskito/JobStatus.java
Comment thread sdks/java/src/main/java/org/byteveda/taskito/spi/QueueBackend.java
Comment thread sdks/java/src/main/java/org/byteveda/taskito/Task.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant