Skip to content

fix(java): harden outcome dispatch and webhook delivery#361

Merged
pratyush618 merged 5 commits into
masterfrom
fix/java-outcome-webhook-hardening
Jul 5, 2026
Merged

fix(java): harden outcome dispatch and webhook delivery#361
pratyush618 merged 5 commits into
masterfrom
fix/java-outcome-webhook-hardening

Conversation

@pratyush618

@pratyush618 pratyush618 commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Fixes the four medium-severity findings from the Java SDK audit follow-up list.

  • Per-middleware fault isolation: one throwing middleware in the outcome loop no longer starves the rest for that event; faults are logged and dispatch continues. Regression test drives a worker with a throwing first middleware and asserts the second still observes the outcome.
  • Per-hook webhook isolation: a bad or stale webhook URL (URI.create throws) no longer kills delivery to later hooks; each delivery failure is logged and the loop continues.
  • Webhook retry backoff: retries were fired back-to-back with zero delay, bursting a down endpoint at up to 4x the outcome rate. Now 500ms doubling to a 30s cap (matching the Node SDK deliverer), with a redacted-URL warning when attempts are exhausted.
  • Webhook list caching: dispatch ran a blocking native settings read plus JSON parse per job outcome on the single outcome-drain thread, even with zero webhooks configured. The parsed list is now cached; local create/delete invalidate immediately and a 30s TTL bounds staleness from writers in other processes.

./gradlew build green (tests, checkstyle, spotless).

Summary by CodeRabbit

  • Bug Fixes
    • Improved webhook delivery reliability by adding exponential-backoff retries for transient transport/server errors, with clearer failure logging when retry limits are reached.
    • Webhook delivery is now more resilient: failures in one matching webhook no longer prevent delivery to others.
    • Middleware failures are isolated during outcome handling so later middlewares still receive the event.
    • Improved log safety by redacting destination URL details in delivery warnings.
    • Cached active webhooks briefly to reduce repeated reloads and parsing during dispatch.
  • Tests
    • Added coverage to ensure a failing middleware does not starve subsequent middlewares.

Zero-delay retries burst a down endpoint; now 500ms doubling to 30s
cap, matching the Node SDK, with a warning on exhaustion.
store.load() did a blocking native read + JSON parse per job outcome
on the single drain thread, even with zero webhooks.
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6a20295-6e6c-4b96-aaf8-e8da0be341ab

📥 Commits

Reviewing files that changed from the base of the PR and between b9455c8 and 8473144.

📒 Files selected for processing (2)
  • sdks/java/src/main/java/org/byteveda/taskito/webhooks/Deliverer.java
  • sdks/java/src/main/java/org/byteveda/taskito/webhooks/WebhookManager.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • sdks/java/src/main/java/org/byteveda/taskito/webhooks/Deliverer.java
  • sdks/java/src/main/java/org/byteveda/taskito/webhooks/WebhookManager.java

📝 Walkthrough

Walkthrough

This PR adds exponential-backoff retry with URI redaction to webhook delivery, introduces a TTL-based cache for active webhooks in WebhookManager with per-hook delivery error isolation, and wraps middleware dispatch calls in WorkerDispatchBridge to prevent one failing middleware from blocking others, plus a corresponding test.

Changes

Delivery reliability and fault isolation

Layer / File(s) Summary
Deliverer retry/backoff and URI redaction
sdks/java/src/main/java/org/byteveda/taskito/webhooks/Deliverer.java
Replaces immediate retries with exponential-backoff scheduling via CompletableFuture.delayedExecutor, caps the delay, logs a warning with a redacted URI on final failure, and adds a redact(URI) helper.
WebhookManager active-hooks caching and dispatch error containment
sdks/java/src/main/java/org/byteveda/taskito/webhooks/WebhookManager.java
Caches active webhooks with a TTL via a CachedHooks record, invalidates the cache on create/delete, switches dispatch to use activeHooks(), and wraps per-hook delivery in try/catch to log and continue on failure.
WorkerDispatchBridge middleware fault isolation and test
sdks/java/src/main/java/org/byteveda/taskito/worker/WorkerDispatchBridge.java, sdks/java/src/test/java/org/byteveda/taskito/WorkerTest.java
Wraps each middleware dispatch call in try/catch to log RuntimeException and continue dispatching, with a new test verifying a failing middleware doesn't block a subsequent one.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WebhookManager
  participant Deliverer
  participant TargetServer

  WebhookManager->>Deliverer: deliver(request)
  Deliverer->>TargetServer: sendWithRetry(attempt=0)
  TargetServer-->>Deliverer: 5xx error / transport failure
  Deliverer->>Deliverer: schedule delayed retry (backoff)
  Deliverer->>TargetServer: sendWithRetry(attempt=1)
  TargetServer-->>Deliverer: failure again
  Deliverer->>Deliverer: retries exhausted, log warning with redact(URI)
Loading
sequenceDiagram
  participant Worker
  participant WorkerDispatchBridge
  participant MiddlewareA
  participant MiddlewareB

  Worker->>WorkerDispatchBridge: onOutcome(event)
  WorkerDispatchBridge->>MiddlewareA: dispatch(event)
  MiddlewareA-->>WorkerDispatchBridge: throws RuntimeException
  WorkerDispatchBridge->>WorkerDispatchBridge: log warning
  WorkerDispatchBridge->>MiddlewareB: dispatch(event)
  MiddlewareB-->>WorkerDispatchBridge: onCompleted (countDown)
Loading

Related issues: None referenced in the provided context.

Related PRs: None referenced in the provided context.

Suggested labels: reliability, webhooks, worker

Suggested reviewers: None specified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: making outcome dispatch and webhook delivery more resilient.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/java-outcome-webhook-hardening

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: 3

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

125-133: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use monotonic time for cache TTL checks.

Line 127 uses wall-clock time for elapsed TTL; a backward clock adjustment can keep stale webhook settings past the intended 30s bound. Prefer System.nanoTime() for elapsed-duration checks.

Proposed fix
     private List<Webhook> activeHooks() {
         CachedHooks snapshot = cached;
-        long now = System.currentTimeMillis();
-        if (snapshot == null || now - snapshot.loadedAt() > CACHE_TTL_MS) {
+        long now = System.nanoTime();
+        if (snapshot == null || now - snapshot.loadedAt() > TimeUnit.MILLISECONDS.toNanos(CACHE_TTL_MS)) {
             snapshot = new CachedHooks(List.copyOf(store.load()), now);
             cached = snapshot;
         }
+import java.util.concurrent.TimeUnit;
🤖 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/webhooks/WebhookManager.java`
around lines 125 - 133, The cache expiration check in activeHooks() is using
wall-clock time, which can be affected by system clock changes. Update the
CachedHooks timestamping and TTL comparison logic to use a monotonic source like
System.nanoTime() for elapsed-duration checks, while keeping the rest of
WebhookManager.activeHooks() behavior unchanged. Make sure the cached loaded-at
value and the now comparison are both based on the same monotonic time source so
stale webhook settings cannot persist past CACHE_TTL_MS.
🤖 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 `@sdks/java/src/main/java/org/byteveda/taskito/webhooks/Deliverer.java`:
- Around line 57-64: The redact(URI) helper in Deliverer still logs the URI
path, which can expose secrets embedded in path segments. Update redact(URI) to
strip path components as well as credentials and query string, so the failure
warning only logs a non-sensitive URI form. Keep the fix localized to the redact
method used by Deliverer’s webhook logging.
- Around line 51-53: The backoff calculation in Deliverer.sendWithRetry should
clamp before shifting, because MAX_BACKOFF_MS is being applied after
`BASE_BACKOFF_MS << attempt` can overflow and produce negative or zero delays.
Update the delay computation to bound the effective shift/exponential growth
before it reaches overflow, while keeping the retry scheduling in
`CompletableFuture.delayedExecutor(...).execute(...)` capped at MAX_BACKOFF_MS.

In `@sdks/java/src/main/java/org/byteveda/taskito/webhooks/WebhookManager.java`:
- Line 119: The warning in Deliverer.deliver should not log the raw exception
object because URI.create(hook.url) can include the original URL in its
IllegalArgumentException message and leak tokens. Update the webhook failure
handling in WebhookManager so the LOG.warn call logs only a sanitized message
(or a redacted exception message) instead of e, while keeping the context around
hook.id and the delivery failure.

---

Nitpick comments:
In `@sdks/java/src/main/java/org/byteveda/taskito/webhooks/WebhookManager.java`:
- Around line 125-133: The cache expiration check in activeHooks() is using
wall-clock time, which can be affected by system clock changes. Update the
CachedHooks timestamping and TTL comparison logic to use a monotonic source like
System.nanoTime() for elapsed-duration checks, while keeping the rest of
WebhookManager.activeHooks() behavior unchanged. Make sure the cached loaded-at
value and the now comparison are both based on the same monotonic time source so
stale webhook settings cannot persist past CACHE_TTL_MS.
🪄 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: 160c3fe4-7719-46c1-8c95-edcce8f12a16

📥 Commits

Reviewing files that changed from the base of the PR and between c34bb31 and b9455c8.

📒 Files selected for processing (4)
  • sdks/java/src/main/java/org/byteveda/taskito/webhooks/Deliverer.java
  • sdks/java/src/main/java/org/byteveda/taskito/webhooks/WebhookManager.java
  • sdks/java/src/main/java/org/byteveda/taskito/worker/WorkerDispatchBridge.java
  • sdks/java/src/test/java/org/byteveda/taskito/WorkerTest.java

Comment thread sdks/java/src/main/java/org/byteveda/taskito/webhooks/Deliverer.java Outdated
Comment thread sdks/java/src/main/java/org/byteveda/taskito/webhooks/Deliverer.java Outdated
Comment thread sdks/java/src/main/java/org/byteveda/taskito/webhooks/WebhookManager.java Outdated
@pratyush618
pratyush618 merged commit af2b723 into master Jul 5, 2026
19 checks passed
@pratyush618
pratyush618 deleted the fix/java-outcome-webhook-hardening branch July 5, 2026 13:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant