Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
438df4c
Add workflow-side signal-with-start through system Nexus
tconley1428 Oct 19, 2026
2764d38
Document workflow signal-with-start APIs in changelog
tconley1428 Oct 19, 2026
7e02a64
Expose generated system APIs directly through Workflow
tconley1428 Oct 19, 2026
cd2fead
Use the shared multi-language workflow service WIT verbatim
tconley1428 Oct 19, 2026
1f3f4db
Format generated system APIs during regeneration
tconley1428 Oct 19, 2026
8dd88e6
Keep generated model converter scoping in SDK internals
tconley1428 Oct 20, 2026
c7c7443
Keep system envelope handling out of CodecDataConverter
tconley1428 Oct 20, 2026
dd65bc8
Preserve serialized embedded payloads in system Nexus models
tconley1428 Oct 20, 2026
01033c8
Revert "Preserve serialized embedded payloads in system Nexus models"
tconley1428 Oct 20, 2026
f2f9c90
Document generic requirements for payload-bearing responses
tconley1428 Oct 20, 2026
6073b77
Expose generated workflow arguments and options API
tconley1428 Oct 20, 2026
eb6b421
Support typed workflow and signal callbacks in generated APIs
tconley1428 Oct 20, 2026
b0d11e0
Probe external stubs for direct bound-method workflow APIs
tconley1428 Oct 21, 2026
5e06831
Merge workflow annotations through generated request hooks
tconley1428 Oct 21, 2026
2871c1c
Keep signal-with-start arguments out of generated options
tconley1428 Oct 21, 2026
c1f8df3
Verify system workflow codec contexts and reject unknown overrides
tconley1428 Oct 21, 2026
a2394fd
Reuse SDK memo payload map conversion
tconley1428 Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,21 @@ to docs, or any other relevant information.
## [Unreleased]

### :boom: Breaking Changes
- `WorkflowOutboundCallsInterceptor` now includes generated system Nexus operation hooks.
Direct implementations must implement the hooks; extending `WorkflowOutboundCallsInterceptorBase`
provides forwarding implementations.
- `WorkflowOutboundCallsInterceptor` has new `sleep(Duration, TimerOptions)` and
`await(Duration, TimerOptions, String, Supplier)` methods. Implementations that don't extend
`WorkflowOutboundCallsInterceptorBase` need to add them, and Base subclasses override them to see calls with options.

### Added
- Workflows can signal with start through the system workflow service using
generated `Workflow.signalWithStartWorkflow(workflowType, args, workflowId, taskQueue, signalName, signalArgs, options)` API.
Typed overloads accept bound workflow/signal methods followed directly by checked arguments.
Typed calls merge workflow retry/cron annotations with explicit options using generated Java-only WIT hooks.
Request builders and operations are generated into `io.temporal.workflow`.
System Nexus envelopes preserve destination workflow serialization context for nested payloads,
codecs, and external payload storage. Requires a server supporting the system workflow service.
- Applications can use the experimental `@TransferTypeConvertible` annotation and
`TransferTypeConverter` interface to convert top-level model values to transfer types. The SDK
applies this conversion to workflow, activity, Nexus, and schedule payloads, including failure
Expand Down
36 changes: 36 additions & 0 deletions temporal-sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies {
// we need to compile the code with Java 21 and package it in a multi-release jar.
// Similarly, Jackson 3 support requires Java 17+ and is compiled separately.
sourceSets {
main.java.srcDir 'src/main/generated'
java17 {
java {
srcDirs = ['src/main/java17']
Expand Down Expand Up @@ -335,3 +336,38 @@ tasks.named('check') {
dependsOn(testing.suites.jackson3Tests)
dependsOn(testing.suites.virtualThreadTests)
}

// Explicit regeneration keeps normal SDK builds independent of the Rust toolchain.
// Use -PnexgenBinary=/path/to/nexgen to test an unreleased generator.
def nexusGeneratedDir = layout.buildDirectory.dir('generated/nexus/workflow').get().asFile
def nexusCheckedInDir = file('src/main/generated/io/temporal/workflow')
tasks.register('generateNexusSystemApi', Exec) {
dependsOn ':temporal-serviceclient:generateProto'
def binary = providers.gradleProperty('nexgenBinary').orElse('nexgen')
inputs.dir('src/main/nexus')
inputs.file(project(':temporal-serviceclient').layout.buildDirectory.file('generated/descriptors/temporal-api.desc'))
inputs.property('nexgenVersion', '0.2.9')
outputs.dir(nexusGeneratedDir)
outputs.upToDateWhen { false }
commandLine binary.get(), 'java', file('src/main/nexus/workflow-service.wit'),
file('src/main/nexus/deps'), '--native-api', '--system-nexus', '--format',
'--support-package', 'io.temporal.internal.nexus.system.TemporalModelConverters',
'--package-name', 'io.temporal.workflow',
'--descriptors', project(':temporal-serviceclient').layout.buildDirectory.file('generated/descriptors/temporal-api.desc').get().asFile,
'--output', nexusGeneratedDir
doFirst {
def versionOutput = new ByteArrayOutputStream()
project.exec { invocation ->
invocation.commandLine(binary.get(), '--version')
invocation.standardOutput = versionOutput
}
if (versionOutput.toString('UTF-8').trim() != 'nexgen 0.2.9') {
throw new GradleException('System Nexus regeneration requires nexgen 0.2.9 with the advanced feature.')
}
delete nexusGeneratedDir
}
doLast { project.sync { spec -> spec.from(nexusGeneratedDir); spec.into(nexusCheckedInDir) } }
}
tasks.matching { it.name == 'compileJava' || it.name.startsWith('spotless') }.configureEach {
mustRunAfter 'generateNexusSystemApi'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Generated by nexgen. DO NOT EDIT!
package io.temporal.workflow;

/** Shared conversion helpers used by these generated models. */
@SuppressWarnings({"unchecked", "rawtypes"})
public final class NexgenRuntime {
private NexgenRuntime() {}

public static <T> java.util.List<T> decodeSequence(
io.temporal.api.common.v1.Payloads payloads,
io.temporal.common.converter.DataConverter converter,
Class<T> elementClass,
java.lang.reflect.Type elementType) {
java.util.List<T> result = new java.util.ArrayList<>();
for (io.temporal.api.common.v1.Payload payload : payloads.getPayloadsList())
result.add(converter.fromPayload(payload, elementClass, elementType));
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
// Generated by nexgen v0.2.9. DO NOT EDIT!
package io.temporal.workflow;

/** Signal a workflow, starting it first if needed. */
@SuppressWarnings({"unchecked", "rawtypes"})
public final class SignalWithStartWorkflowOptions {
/**
* Total workflow execution timeout, including retries and continue-as-new. Defaults to unlimited.
*/
private final java.time.Duration executionTimeout;

/** Timeout of a single workflow run. Defaults to the workflow execution timeout. */
private final java.time.Duration runTimeout;

/** Timeout of a single workflow task. Defaults to 10 seconds. */
private final java.time.Duration taskTimeout;

/** Behavior when a closed workflow with the same ID exists. Default is allow-duplicate. */
private final io.temporal.api.enums.v1.WorkflowIdReusePolicy idReusePolicy;

/**
* Behavior when a workflow is currently running with the same ID. Set to use-existing for
* idempotent deduplication on workflow ID. Cannot be set if id-reuse-policy is
* terminate-if-running. Defaults to use-existing.
*/
private final io.temporal.api.enums.v1.WorkflowIdConflictPolicy idConflictPolicy;

/** Retry policy for the workflow. */
private final io.temporal.common.RetryOptions retryPolicy;

/** Cron schedule for recurring workflow executions. See https://docs.temporal.io/cron-job. */
private final String cronSchedule;

/** Memo for the workflow. */
private final java.util.Map<String, Object> memo;

/** Typed search attributes for the workflow. */
private final io.temporal.common.SearchAttributes searchAttributes;

/** Priority of the workflow execution. */
private final io.temporal.common.Priority priority;

/** Override for workflow versioning behavior. */
private final io.temporal.common.VersioningOverride versioningOverride;

/** Amount of time to wait before starting the workflow. This does not work with cron-schedule. */
private final java.time.Duration startDelay;

/**
* Single-line fixed summary for the workflow execution that may appear in UI and CLI. This can be
* in single-line Temporal Markdown format.
*/
private final String staticSummary;

/**
* General fixed details for the workflow execution that may appear in UI and CLI. This can be in
* Temporal Markdown format and can span multiple lines. This value is fixed on the workflow
* execution and cannot be updated.
*/
private final String staticDetails;

public static SignalWithStartWorkflowOptions getDefaultInstance() {
return SignalWithStartWorkflowOptions.newBuilder().build();
}

private SignalWithStartWorkflowOptions(
java.time.Duration executionTimeout,
java.time.Duration runTimeout,
java.time.Duration taskTimeout,
io.temporal.api.enums.v1.WorkflowIdReusePolicy idReusePolicy,
io.temporal.api.enums.v1.WorkflowIdConflictPolicy idConflictPolicy,
io.temporal.common.RetryOptions retryPolicy,
String cronSchedule,
java.util.Map<String, Object> memo,
io.temporal.common.SearchAttributes searchAttributes,
io.temporal.common.Priority priority,
io.temporal.common.VersioningOverride versioningOverride,
java.time.Duration startDelay,
String staticSummary,
String staticDetails) {
this.executionTimeout = executionTimeout;
this.runTimeout = runTimeout;
this.taskTimeout = taskTimeout;
this.idReusePolicy = idReusePolicy;
this.idConflictPolicy = idConflictPolicy;
this.retryPolicy = retryPolicy;
this.cronSchedule = cronSchedule;
this.memo = memo;
this.searchAttributes = searchAttributes;
this.priority = priority;
this.versioningOverride = versioningOverride;
this.startDelay = startDelay;
this.staticSummary = staticSummary;
this.staticDetails = staticDetails;
}

public java.time.Duration getExecutionTimeout() {
return executionTimeout;
}

public java.time.Duration getRunTimeout() {
return runTimeout;
}

public java.time.Duration getTaskTimeout() {
return taskTimeout;
}

public io.temporal.api.enums.v1.WorkflowIdReusePolicy getIdReusePolicy() {
return idReusePolicy;
}

public io.temporal.api.enums.v1.WorkflowIdConflictPolicy getIdConflictPolicy() {
return idConflictPolicy;
}

public io.temporal.common.RetryOptions getRetryPolicy() {
return retryPolicy;
}

public String getCronSchedule() {
return cronSchedule;
}

public java.util.Map<String, Object> getMemo() {
return memo;
}

public io.temporal.common.SearchAttributes getSearchAttributes() {
return searchAttributes;
}

public io.temporal.common.Priority getPriority() {
return priority;
}

public io.temporal.common.VersioningOverride getVersioningOverride() {
return versioningOverride;
}

public java.time.Duration getStartDelay() {
return startDelay;
}

public String getStaticSummary() {
return staticSummary;
}

public String getStaticDetails() {
return staticDetails;
}

public Builder toBuilder() {
return newBuilder(this);
}

public static Builder newBuilder(SignalWithStartWorkflowOptions value) {
return new Builder(value);
}

public static Builder newBuilder() {
return new Builder();
}

public static final class Builder {
private Builder() {}

private Builder(SignalWithStartWorkflowOptions value) {
if (value == null) return;
this.executionTimeout = value.executionTimeout;
this.runTimeout = value.runTimeout;
this.taskTimeout = value.taskTimeout;
this.idReusePolicy = value.idReusePolicy;
this.idConflictPolicy = value.idConflictPolicy;
this.retryPolicy = value.retryPolicy;
this.cronSchedule = value.cronSchedule;
this.memo = value.memo;
this.searchAttributes = value.searchAttributes;
this.priority = value.priority;
this.versioningOverride = value.versioningOverride;
this.startDelay = value.startDelay;
this.staticSummary = value.staticSummary;
this.staticDetails = value.staticDetails;
}

private java.time.Duration executionTimeout = null;

public Builder setExecutionTimeout(java.time.Duration value) {
this.executionTimeout = value;
return this;
}

private java.time.Duration runTimeout = null;

public Builder setRunTimeout(java.time.Duration value) {
this.runTimeout = value;
return this;
}

private java.time.Duration taskTimeout = null;

public Builder setTaskTimeout(java.time.Duration value) {
this.taskTimeout = value;
return this;
}

private io.temporal.api.enums.v1.WorkflowIdReusePolicy idReusePolicy =
io.temporal.api.enums.v1.WorkflowIdReusePolicy.forNumber(1);

public Builder setIdReusePolicy(io.temporal.api.enums.v1.WorkflowIdReusePolicy value) {
this.idReusePolicy = value;
return this;
}

private io.temporal.api.enums.v1.WorkflowIdConflictPolicy idConflictPolicy = null;

public Builder setIdConflictPolicy(io.temporal.api.enums.v1.WorkflowIdConflictPolicy value) {
this.idConflictPolicy = value;
return this;
}

private io.temporal.common.RetryOptions retryPolicy = null;

public Builder setRetryPolicy(io.temporal.common.RetryOptions value) {
this.retryPolicy = value;
return this;
}

private String cronSchedule = null;

public Builder setCronSchedule(String value) {
this.cronSchedule = value;
return this;
}

private java.util.Map<String, Object> memo = null;

public Builder setMemo(java.util.Map<String, Object> value) {
this.memo = value;
return this;
}

private io.temporal.common.SearchAttributes searchAttributes = null;

public Builder setSearchAttributes(io.temporal.common.SearchAttributes value) {
this.searchAttributes = value;
return this;
}

private io.temporal.common.Priority priority = null;

public Builder setPriority(io.temporal.common.Priority value) {
this.priority = value;
return this;
}

private io.temporal.common.VersioningOverride versioningOverride = null;

public Builder setVersioningOverride(io.temporal.common.VersioningOverride value) {
this.versioningOverride = value;
return this;
}

private java.time.Duration startDelay = null;

public Builder setStartDelay(java.time.Duration value) {
this.startDelay = value;
return this;
}

private String staticSummary = null;

public Builder setStaticSummary(String value) {
this.staticSummary = value;
return this;
}

private String staticDetails = null;

public Builder setStaticDetails(String value) {
this.staticDetails = value;
return this;
}

public SignalWithStartWorkflowOptions build() {
return new SignalWithStartWorkflowOptions(
executionTimeout,
runTimeout,
taskTimeout,
idReusePolicy,
idConflictPolicy,
retryPolicy,
cronSchedule,
memo,
searchAttributes,
priority,
versioningOverride,
startDelay,
staticSummary,
staticDetails);
}
}
}
Loading
Loading