From c0a26ff4f06d4363e120c9d19e4493763af5290c Mon Sep 17 00:00:00 2001 From: Greg Travis Date: Wed, 1 Apr 2026 14:22:10 -0400 Subject: [PATCH 1/2] Expose static_summary and static_details fields in workflow init. --- .../internal/replay/BasicWorkflowContext.java | 11 +++- .../replay/ReplayWorkflowContext.java | 6 ++ .../replay/ReplayWorkflowContextImpl.java | 29 ++++++++- .../replay/ReplayWorkflowRunTaskHandler.java | 6 +- .../internal/sync/WorkflowInternal.java | 20 ++++++ .../java/io/temporal/workflow/Workflow.java | 25 ++++++++ .../workflow/WorkflowStaticSummaryTest.java | 62 +++++++++++++++++++ .../sync/DummySyncWorkflowContext.java | 12 ++++ 8 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 temporal-sdk/src/test/java/io/temporal/workflow/WorkflowStaticSummaryTest.java diff --git a/temporal-sdk/src/main/java/io/temporal/internal/replay/BasicWorkflowContext.java b/temporal-sdk/src/main/java/io/temporal/internal/replay/BasicWorkflowContext.java index 0bffd6e1bc..f33d2fff6f 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/replay/BasicWorkflowContext.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/replay/BasicWorkflowContext.java @@ -7,6 +7,7 @@ import io.temporal.api.common.v1.*; import io.temporal.api.failure.v1.Failure; import io.temporal.api.history.v1.WorkflowExecutionStartedEventAttributes; +import io.temporal.api.sdk.v1.UserMetadata; import io.temporal.common.RetryOptions; import io.temporal.internal.common.ProtobufTimeUtils; import java.time.Duration; @@ -24,6 +25,7 @@ final class BasicWorkflowContext { private final WorkflowExecutionStartedEventAttributes startedAttributes; private final String namespace; @Nonnull private final WorkflowExecution workflowExecution; + @Nullable private final UserMetadata userMetadata; @Nullable private final Payloads lastCompletionResult; @@ -33,11 +35,13 @@ final class BasicWorkflowContext { String namespace, @Nonnull WorkflowExecution workflowExecution, WorkflowExecutionStartedEventAttributes startedAttributes, - long runStartedTimestampMillis) { + long runStartedTimestampMillis, + @Nullable UserMetadata userMetadata) { this.namespace = namespace; this.workflowExecution = Preconditions.checkNotNull(workflowExecution); this.startedAttributes = startedAttributes; this.runStartedTimestampMillis = runStartedTimestampMillis; + this.userMetadata = userMetadata; this.lastCompletionResult = startedAttributes.hasLastCompletionResult() ? startedAttributes.getLastCompletionResult() @@ -144,4 +148,9 @@ public RetryOptions getRetryOptions() { public Priority getPriority() { return startedAttributes.getPriority(); } + + @Nullable + public UserMetadata getUserMetadata() { + return userMetadata; + } } diff --git a/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContext.java b/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContext.java index 19a488e775..718960b42d 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContext.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContext.java @@ -125,6 +125,12 @@ public Functions.Proc1 getCancellationHandle() { Payload getMemo(String key); + @Nullable + Payload getStaticSummaryPayload(); + + @Nullable + Payload getStaticDetailsPayload(); + /** * Requests an activity execution. * diff --git a/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContextImpl.java b/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContextImpl.java index dd1844a316..e8db5d7a28 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContextImpl.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContextImpl.java @@ -50,11 +50,16 @@ final class ReplayWorkflowContextImpl implements ReplayWorkflowContext { long runStartedTimestampMillis, @Nullable String fullReplayDirectQueryName, SingleWorkerOptions workerOptions, - Scope workflowMetricsScope) { + Scope workflowMetricsScope, + @Nullable UserMetadata userMetadata) { this.workflowStateMachines = workflowStateMachines; this.basicWorkflowContext = new BasicWorkflowContext( - namespace, workflowExecution, startedAttributes, runStartedTimestampMillis); + namespace, + workflowExecution, + startedAttributes, + runStartedTimestampMillis, + userMetadata); this.mutableState = new WorkflowMutableState(startedAttributes); this.fullReplayDirectQueryName = fullReplayDirectQueryName; this.replayAwareWorkflowMetricsScope = @@ -277,6 +282,26 @@ public Priority getPriority() { return basicWorkflowContext.getPriority(); } + @Override + @Nullable + public Payload getStaticSummaryPayload() { + UserMetadata userMetadata = basicWorkflowContext.getUserMetadata(); + if (userMetadata == null || !userMetadata.hasSummary()) { + return null; + } + return userMetadata.getSummary(); + } + + @Override + @Nullable + public Payload getStaticDetailsPayload() { + UserMetadata userMetadata = basicWorkflowContext.getUserMetadata(); + if (userMetadata == null || !userMetadata.hasDetails()) { + return null; + } + return userMetadata.getDetails(); + } + @Override public Functions.Proc1 newTimer( Duration delay, UserMetadata metadata, Functions.Proc1 callback) { diff --git a/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowRunTaskHandler.java b/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowRunTaskHandler.java index 1d0d09a692..ed721ec133 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowRunTaskHandler.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowRunTaskHandler.java @@ -19,6 +19,7 @@ import io.temporal.api.protocol.v1.Message; import io.temporal.api.query.v1.WorkflowQuery; import io.temporal.api.query.v1.WorkflowQueryResult; +import io.temporal.api.sdk.v1.UserMetadata; import io.temporal.api.workflowservice.v1.GetSystemInfoResponse; import io.temporal.api.workflowservice.v1.PollWorkflowTaskQueueResponseOrBuilder; import io.temporal.internal.Config; @@ -89,6 +90,8 @@ class ReplayWorkflowRunTaskHandler implements WorkflowRunTaskHandler { "First event in the history is not WorkflowExecutionStarted"); } this.startedEvent = startedEvent.getWorkflowExecutionStartedEventAttributes(); + UserMetadata userMetadata = + startedEvent.hasUserMetadata() ? startedEvent.getUserMetadata() : null; this.metricsScope = metricsScope; this.localActivityDispatcher = localActivityDispatcher; this.workflow = workflow; @@ -111,7 +114,8 @@ class ReplayWorkflowRunTaskHandler implements WorkflowRunTaskHandler { Timestamps.toMillis(startedEvent.getEventTime()), fullReplayDirectQueryType, workerOptions, - metricsScope); + metricsScope, + userMetadata); this.replayWorkflowExecutor = new ReplayWorkflowExecutor(workflow, workflowStateMachines, context); diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowInternal.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowInternal.java index 79495694b4..47a3d680a7 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowInternal.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowInternal.java @@ -855,6 +855,26 @@ public static String getCurrentDetails() { return getRootWorkflowContext().getCurrentDetails(); } + @Nullable + public static String getStaticSummary() { + Payload payload = getRootWorkflowContext().getReplayContext().getStaticSummaryPayload(); + if (payload == null) { + return null; + } + return getDataConverterWithCurrentWorkflowContext() + .fromPayload(payload, String.class, String.class); + } + + @Nullable + public static String getStaticDetails() { + Payload payload = getRootWorkflowContext().getReplayContext().getStaticDetailsPayload(); + if (payload == null) { + return null; + } + return getDataConverterWithCurrentWorkflowContext() + .fromPayload(payload, String.class, String.class); + } + @Nullable public static Object getInstance() { return getRootWorkflowContext().getInstance(); diff --git a/temporal-sdk/src/main/java/io/temporal/workflow/Workflow.java b/temporal-sdk/src/main/java/io/temporal/workflow/Workflow.java index 61c787757f..ac5ada19d7 100644 --- a/temporal-sdk/src/main/java/io/temporal/workflow/Workflow.java +++ b/temporal-sdk/src/main/java/io/temporal/workflow/Workflow.java @@ -1554,6 +1554,31 @@ public static String getCurrentDetails() { return WorkflowInternal.getCurrentDetails(); } + /** + * Get the fixed summary for this workflow execution that was set at workflow start. This can be + * in single-line Temporal markdown format. + * + * @return the static summary, or null if not set + */ + @Experimental + @Nullable + public static String getStaticSummary() { + return WorkflowInternal.getStaticSummary(); + } + + /** + * Get the fixed details for this workflow execution that were set at workflow start. This can be + * in Temporal markdown format and can span multiple lines. Unlike {@link #getCurrentDetails()}, + * this value cannot be updated during workflow execution. + * + * @return the static details, or null if not set + */ + @Experimental + @Nullable + public static String getStaticDetails() { + return WorkflowInternal.getStaticDetails(); + } + /** * Get the currently running workflow instance. * diff --git a/temporal-sdk/src/test/java/io/temporal/workflow/WorkflowStaticSummaryTest.java b/temporal-sdk/src/test/java/io/temporal/workflow/WorkflowStaticSummaryTest.java new file mode 100644 index 0000000000..2373a48848 --- /dev/null +++ b/temporal-sdk/src/test/java/io/temporal/workflow/WorkflowStaticSummaryTest.java @@ -0,0 +1,62 @@ +package io.temporal.workflow; + +import static io.temporal.testing.internal.SDKTestOptions.newWorkflowOptionsWithTimeouts; +import static org.junit.Assert.assertEquals; + +import io.temporal.client.WorkflowOptions; +import io.temporal.testing.internal.SDKTestWorkflowRule; +import org.junit.Rule; +import org.junit.Test; + +public class WorkflowStaticSummaryTest { + + @Rule + public SDKTestWorkflowRule testWorkflowRule = + SDKTestWorkflowRule.newBuilder() + .setWorkflowTypes(WorkflowReadingStaticMetadata.class) + .build(); + + static final String SUMMARY = "my-static-summary"; + static final String DETAILS = "my-static-details"; + + @Test + public void testGetStaticSummaryAndDetails() { + WorkflowOptions options = + newWorkflowOptionsWithTimeouts(testWorkflowRule.getTaskQueue()).toBuilder() + .setStaticSummary(SUMMARY) + .setStaticDetails(DETAILS) + .build(); + TestStaticMetadataWorkflow stub = + testWorkflowRule + .getWorkflowClient() + .newWorkflowStub(TestStaticMetadataWorkflow.class, options); + + String result = stub.execute(); + assertEquals(SUMMARY + "|" + DETAILS, result); + } + + @Test + public void testGetStaticSummaryAndDetailsWhenNotSet() { + WorkflowOptions options = newWorkflowOptionsWithTimeouts(testWorkflowRule.getTaskQueue()); + TestStaticMetadataWorkflow stub = + testWorkflowRule + .getWorkflowClient() + .newWorkflowStub(TestStaticMetadataWorkflow.class, options); + + String result = stub.execute(); + assertEquals("null|null", result); + } + + @WorkflowInterface + public interface TestStaticMetadataWorkflow { + @WorkflowMethod + String execute(); + } + + public static class WorkflowReadingStaticMetadata implements TestStaticMetadataWorkflow { + @Override + public String execute() { + return Workflow.getStaticSummary() + "|" + Workflow.getStaticDetails(); + } + } +} diff --git a/temporal-testing/src/main/java/io/temporal/internal/sync/DummySyncWorkflowContext.java b/temporal-testing/src/main/java/io/temporal/internal/sync/DummySyncWorkflowContext.java index 9f7aa44b6d..7aab9e2b8d 100644 --- a/temporal-testing/src/main/java/io/temporal/internal/sync/DummySyncWorkflowContext.java +++ b/temporal-testing/src/main/java/io/temporal/internal/sync/DummySyncWorkflowContext.java @@ -164,6 +164,18 @@ public Payload getMemo(String key) { throw new UnsupportedOperationException("not implemented"); } + @Override + @Nullable + public Payload getStaticSummaryPayload() { + return null; + } + + @Override + @Nullable + public Payload getStaticDetailsPayload() { + return null; + } + @Override @Nullable public SearchAttributes getSearchAttributes() { From f98a66d8bffce1517d6a4c4dec37389c8b7ada0e Mon Sep 17 00:00:00 2001 From: Greg Travis Date: Thu, 2 Apr 2026 10:42:42 -0400 Subject: [PATCH 2/2] Simplify by assuming UserMetadata always present. --- .../internal/replay/BasicWorkflowContext.java | 6 +++--- .../internal/replay/ReplayWorkflowContextImpl.java | 12 +++--------- .../replay/ReplayWorkflowRunTaskHandler.java | 3 +-- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/temporal-sdk/src/main/java/io/temporal/internal/replay/BasicWorkflowContext.java b/temporal-sdk/src/main/java/io/temporal/internal/replay/BasicWorkflowContext.java index f33d2fff6f..7a0d2dc05f 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/replay/BasicWorkflowContext.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/replay/BasicWorkflowContext.java @@ -25,7 +25,7 @@ final class BasicWorkflowContext { private final WorkflowExecutionStartedEventAttributes startedAttributes; private final String namespace; @Nonnull private final WorkflowExecution workflowExecution; - @Nullable private final UserMetadata userMetadata; + @Nonnull private final UserMetadata userMetadata; @Nullable private final Payloads lastCompletionResult; @@ -36,7 +36,7 @@ final class BasicWorkflowContext { @Nonnull WorkflowExecution workflowExecution, WorkflowExecutionStartedEventAttributes startedAttributes, long runStartedTimestampMillis, - @Nullable UserMetadata userMetadata) { + @Nonnull UserMetadata userMetadata) { this.namespace = namespace; this.workflowExecution = Preconditions.checkNotNull(workflowExecution); this.startedAttributes = startedAttributes; @@ -149,7 +149,7 @@ public Priority getPriority() { return startedAttributes.getPriority(); } - @Nullable + @Nonnull public UserMetadata getUserMetadata() { return userMetadata; } diff --git a/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContextImpl.java b/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContextImpl.java index ee3d5135cd..802fbf9b8d 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContextImpl.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContextImpl.java @@ -52,7 +52,7 @@ final class ReplayWorkflowContextImpl implements ReplayWorkflowContext { @Nullable String fullReplayDirectQueryName, SingleWorkerOptions workerOptions, Scope workflowMetricsScope, - @Nullable UserMetadata userMetadata) { + @Nonnull UserMetadata userMetadata) { this.workflowStateMachines = workflowStateMachines; this.basicWorkflowContext = new BasicWorkflowContext( @@ -287,20 +287,14 @@ public Priority getPriority() { @Nullable public Payload getStaticSummaryPayload() { UserMetadata userMetadata = basicWorkflowContext.getUserMetadata(); - if (userMetadata == null || !userMetadata.hasSummary()) { - return null; - } - return userMetadata.getSummary(); + return userMetadata.hasSummary() ? userMetadata.getSummary() : null; } @Override @Nullable public Payload getStaticDetailsPayload() { UserMetadata userMetadata = basicWorkflowContext.getUserMetadata(); - if (userMetadata == null || !userMetadata.hasDetails()) { - return null; - } - return userMetadata.getDetails(); + return userMetadata.hasDetails() ? userMetadata.getDetails() : null; } @Override diff --git a/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowRunTaskHandler.java b/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowRunTaskHandler.java index ed721ec133..4d1aad9f15 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowRunTaskHandler.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowRunTaskHandler.java @@ -90,8 +90,7 @@ class ReplayWorkflowRunTaskHandler implements WorkflowRunTaskHandler { "First event in the history is not WorkflowExecutionStarted"); } this.startedEvent = startedEvent.getWorkflowExecutionStartedEventAttributes(); - UserMetadata userMetadata = - startedEvent.hasUserMetadata() ? startedEvent.getUserMetadata() : null; + UserMetadata userMetadata = startedEvent.getUserMetadata(); this.metricsScope = metricsScope; this.localActivityDispatcher = localActivityDispatcher; this.workflow = workflow;