AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Resonate Recursive Fan Out Pattern Java

skill-resonatehq-resonate-skills-resonate-recursive-fan-out-pattern-java · by resonatehq

Implement recursive fan-out / parallel workflow execution in Java with Resonate — dispatch all children via ctx.run or ctx.rpc collecting a List of ResonateFuture, then await each. Covers single-workflow fan-out, cross-worker recursion via ctx.rpc to a named worker group, and the worker/client split. Use when processing batches, trees, or graphs where each child is independently durable and optio…

— No reviews yet
0 installs
36 views
0.0% view→install

Install

$ agentstack add skill-resonatehq-resonate-skills-resonate-recursive-fan-out-pattern-java

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ✓ Network access No
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ● Environment & secrets Used
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-resonatehq-resonate-skills-resonate-recursive-fan-out-pattern-java)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Resonate Recursive Fan Out Pattern Java? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Resonate Recursive Fan-Out Pattern — Java

> Prerelease note. resonate-sdk-java is published on Maven Central — pin io.resonatehq:resonate-sdk-java:0.1.1. The API mirrors the Python SDK and may change before a stable 1.0. Requires Java 21+ (virtual threads, a feature generally available in Java 21). Every code block here is compile-verified against 0.1.1 and drawn from the example-fan-out-fan-in-java / example-recursive-factorial-java repos and develop/java.mdx (docs PR #230).

Overview

Recursive fan-out dispatches multiple child invocations in parallel, awaits them individually, and optionally recurses deeper. The Java expression is a two-loop pattern: a dispatch loop builds a List>, then a separate await loop reads each result. Mixing dispatch and await serializes the children — the single most common mistake.

For the language-agnostic mental model (promise deduplication, load-balancing, detached vs. result-gathering), see resonate-recursive-fan-out-pattern-typescript.

When to use

  • Batch processing where items are independent and each needs durability
  • Map-reduce shaped workflows (fan out N workers, aggregate results)
  • Recursive tree / graph traversal with dynamic depth
  • Any case where a crash mid-fan-out must resume, not restart from zero

Dispatch-then-await shape

The canonical fan-out from example-fan-out-fan-in-java — dispatch all children first, await all second:

import io.resonatehq.resonate.Context;
import io.resonatehq.resonate.Context.ResonateFuture;
import java.util.ArrayList;
import java.util.List;

public record Delivery(String channel, boolean ok) {}

public static List fanout(Context ctx, List channels, String message) {
    // Fan out: spawn a child per channel, collecting futures without awaiting yet.
    List> futures = new ArrayList<>();
    for (String channel : channels) {
        futures.add(ctx.run(FanOutFanIn::send, channel, message));
    }

    // Fan in: await every child and aggregate.
    List delivered = new ArrayList<>();
    for (ResonateFuture future : futures) {
        delivered.add(future.await());
    }
    return delivered;
}

public static Delivery send(Context ctx, String channel, String message) {
    return new Delivery(channel, true);
}

The dispatch loop runs serially in user code, but each ctx.run creates an independent durable promise — all children execute concurrently. The await loop only reads results; it does not gate execution of siblings.

Partial failure. As written, an exception from any child's future.await() propagates and stops the loop (fail-fast). To collect every result instead — recording per-child failures rather than aborting at the first — wrap each future.await() in its own try/catch inside the await loop and append a failure record on the catch. Choose fail-fast when one bad child should abort the batch, collect-all when you want a summary of every delivery.

Recursive fan-out across workers via ctx.rpc

A workflow can call ctx.rpc(NAME, smallerArgs) to recurse through the server. Each recursive call is its own durable promise; with multiple workers registered under the same name, the recursion fans out across them. From example-recursive-factorial-java:

import io.resonatehq.resonate.Context;
import io.resonatehq.resonate.Context.Opts;

public final class Factorial {
    private Factorial() {}

    public static final String NAME = "factorial";
    public static final String WORKER_GROUP = "factorial-workers";

    /**
     * Compute n! by recursively dispatching factorial(n-1) to the worker group via ctx.rpc.
     * Returns long — 13! overflows int. The by-name RPC result is read via Number because Jackson
     * decodes a small JSON integer as Integer and a large one as Long.
     */
    public static long factorial(Context ctx, int n) {
        if (n ` — hence the `Number` read. The base case (`if (n  0 ? Integer.parseInt(args[0]) : 6;
    String url = System.getenv().getOrDefault("RESONATE_URL", "http://localhost:8001");

    Resonate r = Resonate.builder().url(url).group("factorial-client").build();
    try {
        String id = "factorial-" + n; // stable id — a second run returns the cached result
        ResonateHandle handle =
                r.options(new Opts().withTarget(Factorial.WORKER_GROUP)).rpc(id, Factorial.NAME, n);
        long result = ((Number) handle.result()).longValue();
        System.out.printf("factorial(%d) = %d%n", n, result);
    } finally {
        r.stop();
    }
}

The client never registers factorial — it only dispatches by name to the worker group. Routing through a non-default group keeps clients out of the task pool so only real workers execute the recursion. Start a second worker before invoking and the intermediate steps distribute across both processes.

ctx.run vs ctx.rpc for fan-out

| | ctx.run | ctx.rpc | |---|---|---| | Execution target | Same process | Remote process (by name or method reference) | | Fan-out across workers | No | Yes — server distributes to the named group | | Recursion across workers | No | Yes — each ctx.rpc(NAME, ...) re-dispatches | | Best for | In-process leaf tasks | Distributed branches, cross-worker recursion |

Both methods return a ResonateFuture immediately and both support the two-loop fan-out pattern. ctx.run functions must return promptly — long-running or blocking work belongs in ctx.rpc.

Distinct Java idioms

  • List> collected in the dispatch loop, drained in the await loop — the two-loop pattern. A single loop that dispatches and awaits in the same iteration serializes children.
  • Typed futures from method references, untyped from by-name rpc — ctx.run(Owner::send, ...) gives ResonateFuture; ctx.rpc(NAME, ...) gives ResonateFuture, so read numerics through Number.
  • group(...) on the builder for the worker group, and a distinct group for the client — Java sets the group directly on the builder, not on a separate transport.
  • Shared NAME / WORKER_GROUP constants in a class imported by both worker and client — prevents string drift between the worker's group(...) and the client's withTarget(...).
  • Stable id for idempotent recursion — "factorial-" + n means a second invocation returns the cached result instead of recomputing.
  • new CountDownLatch(1).await() keeps a worker alive; never r.stop() a worker.
  • Replay safety is automatic — the body re-runs on resume, the dispatch loop deterministically re-issues the same calls, and settled children short-circuit. No replay-guard code needed.

Avoid

  • Awaiting inside the dispatch loop — ctx.run/ctx.rpc immediately followed by await in the same iteration runs children sequentially. Collect all futures first, then await.
  • Unbounded recursion without a base case — the server-side promise graph is not free; always guard ctx.rpc(NAME, ...) with a termination condition (if (n (or any generic) back from a by-name rpc** — the by-name form decodes against Object, so Jackson hands back List, not your record type. Use the method-reference form (ctx.rpc(Owner::fn, ...) / ctx.run(Owner::fn, ...)) for a typed future. See resonate-basic-debugging-java.

Related skills

  • resonate-basic-durable-world-usage-java — ctx.run, ctx.rpc, ResonateFuture.await, Opts
  • resonate-basic-ephemeral-world-usage-java — the worker/client builder setup, group(...), typed vs untyped handles
  • resonate-saga-pattern-java — fan-out alongside compensating transactions
  • durable-execution — foundational replay semantics
  • resonate-recursive-fan-out-pattern-typescript — canonical mental model, deduplication strategy
  • resonate-recursive-fan-out-pattern-python — the closest sibling; the Java API mirrors Python

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.