Skip to content

Add workflow-side signal-with-start through system Nexus - #3154

Draft
tconley1428 wants to merge 19 commits into
mainfrom
java-signal-with-start
Draft

tconley1428 wants to merge 19 commits into
mainfrom
java-signal-with-start

Conversation

@tconley1428

@tconley1428 tconley1428 commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Java workflows can signal an existing workflow or start it and send its first signal through generated workflow-package APIs. Both forms return an ExternalWorkflowStub:

SignalWithStartWorkflowOptions options =
    SignalWithStartWorkflowOptions.newBuilder().setTaskQueue(taskQueue).build();
Target target = Workflow.newExternalWorkflowStub(Target.class, id);
Workflow.signalWithStartWorkflow(target::run, input, target::notify, signal, options);

Workflow.signalWithStartWorkflow(
    "Target", new Object[] {input}, "notify", new Object[] {signal},
    options.toBuilder().setId(id).build());

Only function references/names and their inputs remain direct parameters. Every other field belongs to mandatory generated options. Typed overloads support zero through six workflow arguments and zero or one signal argument, preserving argument/result type checks for value-returning and void workflows. Larger arities use the name-based API and explicit Object[] arguments. Shared generated private helpers avoid repeating validation and request construction.

Typed workflow ID defaults from the workflow reference. Explicit IDs must match; empty workflow IDs and references pinned to a run ID are rejected before dispatch. The signal reference contributes its method name and arguments; its workflow/run IDs are ignored. Options remain reusable across workflow references. Untyped calls require an explicit ID at invocation. Task queue remains required when building options; its automatic default is deferred.

Java-only merge-from-function annotations in the shared WIT select identity, retry, and cron helpers. Retry uses the workflow method's MethodRetry annotation and explicit overrides; cron uses CronSchedule unless explicitly overridden, including empty-string disabling. Signal annotations do not contribute. ExternalWorkflowInvocationHandler probes bound references without issuing commands and retains original method metadata, execution, arguments, and result types. No WorkflowOptions is synthesized.

The conversion helper is package-private in io.temporal.workflow alongside generated callers. The nexgen sample helper contains its own matching implementation rather than delegating to SDK support. Helpers reuse existing SDK conversion utilities where available. SystemNexusDataConverter owns outer-envelope handling and converter scope, independently of the concrete user converter. CodecDataConverter is unchanged. Destination namespace and resolved workflow ID select serialization context. The shared payload visitor recognizes system envelopes and derives roots from generated operation metadata. Request ID remains server-owned and omitted.

Signal-with-start receives no embedded user payloads. Future payload responses need generic models and concrete result types. The known packing-codec limitation in request-model deserialization remains outside this operation's call path.

Shared WIT copies are byte-identical to nexgen. An explicit Gradle task regenerates formatted sources; ordinary SDK builds need neither nexgen nor Rust.

Validation for the latest changes: 18 focused converter/probe/workflow API tests and two real-server signing-codec/replay tests passed. Tests cover inherited IDs, conflicts/run restrictions before dispatch, ignored signal execution identity, annotation merging, concurrent destination contexts, wire inspection, and replay. Companion Java samples passed 51 compiler cases and round-trip coverage; nine generator tests passed. Formatting passed. Full generator validation passed on earlier revisions and was not rerun for this iteration. The full SDK suite is unverified.

Breaking changes: adds public APIs and generated outbound interceptor hooks; direct interceptor implementations must implement the hooks or extend the forwarding base.

Server PR: none included. Requires a server supporting the reserved system workflow-service operation.

Companion generator: temporalio/nexgen#245. Formatter PR temporalio/nexgen#246 has merged.

Comment on lines +10 to +11
Object[] args,
String id,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Even required non-function arguments go in the options struct.

"notify",
new Object[] {"signal"},
options);
ExternalWorkflowStub async =

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Silly for untyped.

Comment on lines +186 to +187
ExternalWorkflowStub defaults =
Workflow.signalWithStartWorkflow(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not needed if options is required

requestBuilder.setSignal(capturedName);
requestBuilder.setSignalArgs(java.util.Arrays.asList(capturedArguments));
});
if (options != null) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Let's share more of these validation helpers.

io.temporal.workflow.Functions.Func1<WorkflowArg1, WorkflowResult> workflow,
WorkflowArg1 workflowArg1,
String id,
String taskQueue,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Task queue can have default derived from workflow context.

package io.temporal.workflow;

/** Generated typed system Nexus hooks. */
public interface SystemNexusWorkflowOutboundInterceptor {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Let's review interceptor functionality.

This branch has not been deployed

No deployments
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