Two primary .NET Workflow versioning methods
The two primary Versioning methods for .NET Workflows are: (1) Worker Versioning, which allows tagging Workers and programmatically rolling them out in versioned deployments so that old Workers can run old code paths and new Workers can run new code paths; (2) Versioning with Patching, which works by adding branches to code tied to specific revisions, applying code changes to new Workflow Executions while avoiding disruptive changes to in-progress Workflow Executions.
.NET Patching is a three-step process
The .NET patching process consists of three steps: (1) Use Patched() to patch in new code and run it alongside the old code; (2) Remove the old code and apply DeprecatePatch(); (3) Once all old Workflows have left retention, remove DeprecatePatch().
.NET Patched() method inserts marker into Event History
Using Workflow.Patched() inserts a marker into the Event History. During replay, if a Worker encounters a history with that marker, it will fail the Workflow task when the Workflow code doesn't produce the same patch marker. This ensures safe deployment of new code as a feature flag alongside the original version.
.NET DeprecatePatch() function behavior
DeprecatePatch() is used after all Workflows started with the old code have left retention. Deprecated patches function similarly to regular patches by adding a marker to the Event History, but this marker won't cause a replay failure when the Workflow code doesn't produce it. This ensures that if old Workers running the original code pick up Workflow histories generated by new code, they will safely use the patched branch.
.NET Patched() basic usage example
if (Workflow.Patched("my-patch"))
{
this.result = await Workflow.ExecuteActivityAsync(
(MyActivities a) => a.PostPatchActivity(),
new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) });
}
else
{
this.result = await Workflow.ExecuteActivityAsync(
(MyActivities a) => a.PrePatchActivity(),
new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) });
}
.NET DeprecatePatch() usage example
Workflow.DeprecatePatch("my-patch");
this.result = await Workflow.ExecuteActivityAsync(
(MyActivities a) => a.PostPatchActivity(),
new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) });
.NET Workflow cutover as versioning alternative
Workflow cutovers are an alternative to patching where a whole new Workflow is created by duplicating the Workflow Definition function with a different name. Both the original and new Workflow types are registered with the Worker. The downside is that this requires code duplication and updating any commands used to start the Workflow, and it does not provide a way to version still-running Workflows—it is essentially just a cutover, unlike Patching.
.NET Workflow cutover example with dual registration
using var worker = new TemporalWorker(
client,
new TemporalWorkerOptions("greeting-tasks")
.AddWorkflow<SayHelloWorkflow>()
.AddWorkflow<SayHelloWorkflowV2>());
Worker Versioning as primary .NET versioning method
Worker Versioning is a feature that allows tagging Workers and programmatically rolling them out in Deployment Versions, so that old Workers can run old code paths and new Workers can run new code paths. This allows pinning Workflows to specific revisions, avoiding the need for patching.
Versioning methods in Go SDK
Temporal Go SDK supports two primary Versioning methods: Worker Versioning, which allows tagging Workers and programmatically rolling them out in Deployment Versions so old Workers run old code paths and new Workers run new code paths; and Versioning with Patching, which works by adding branches to code tied to specific revisions.
Worker Versioning experimental support removal timeline
Support for the experimental Worker Versioning method before 2025 will be removed from Temporal Server in March 2026.
GetVersion API parameters and behavior
workflow.GetVersion() takes three parameters: a changeID string, minSupported version, and maxSupported version. When GetVersion runs for a new Workflow Execution, it records a marker in the Event History so all future calls to GetVersion for this changeID on this Workflow Execution will always return the same version number.
GetVersion return values for different execution states
A Workflow Execution that has not reached a GetVersion call before it was introduced returns DefaultVersion. A Workflow Execution that was run with maxSupported set to a specific version returns that version number. New Workflows return the current maxSupported version.
GetVersion minSupported parameter semantics
The minSupported parameter in GetVersion defines the minimum version that can be replayed. If an older version of Workflow Execution history is replayed on code with a higher minSupported value, it fails because the minimum expected version is not met.
Preserving GetVersion calls after version cleanup
After removing all code branches for old versions, you should preserve the first call to GetVersion() for each changeID. This ensures that if a Workflow Execution still running for an older version is replayed, it will fail at this point rather than proceeding with mismatched code. Additionally, preserving the call makes it easier to add new versions later by only updating maxSupported.
Removing GetVersion calls completely
You can remove the first GetVersion call only after ensuring all executions with an older version have left retention. However, once removed, you cannot reuse the same changeID for future changes; you must use a different changeID like 'Step1-fix2' and start minVersion from DefaultVersion again.
Workflow cutover as versioning alternative
Workflow cutover is an alternative to Patching that involves creating a new Workflow Definition with a different name (for example, PizzaWorkflowV2 instead of PizzaWorkflow), copying the original code with changes, registering both Workflow Types with the Worker, and updating commands to start the new Workflow type. The downside is code duplication and the need to update any commands used to start the Workflow.
Deprecating old Workflow versions using List Filter
You can safely remove support for older Workflow versions once certain no open Workflow Executions based on that version exist. Use List Filter syntax: WorkflowType = "PizzaWorkflow" AND ExecutionStatus = "Running" AND TemporalChangeVersion="ChangedNotificationActivityType-1" to check for running executions with a specific version marker.
Checking for Workflow Executions without version markers
Workflow Executions started before GetVersion was added to the code will not have the associated Marker in their Event History. Use the query: WorkflowType = "PizzaWorkflow" AND ExecutionStatus = "Running" AND TemporalChangeVersion IS NULL to identify any of these still-running executions.
GetVersion example with multiple versions
When making additional changes after an initial GetVersion, increase maxSupported and add new branches. For example, if changing from ActivityC to ActivityD after previously changing from ActivityA to ActivityC, use: v := workflow.GetVersion(ctx, "Step1", workflow.DefaultVersion, 2) with branches for DefaultVersion (ActivityA), v==1 (ActivityC), and else (ActivityD).
GetVersion cleanup after retention
After all Workflow Executions for an older version have left retention, you can remove the code branch for that version by increasing minSupported. For example, change from minSupported=DefaultVersion to minSupported=1 to stop supporting workflows with no version marker.
Continue-As-New helper method for streams
Use stream.NewContinueAsNewError(ctx, wfn, buildArgs) to invoke a Continue-As-New rollover that preserves stream continuity. The helper drains waiting subscribers, waits for in-flight handlers to finish, then returns a Continue-As-New error built from the args produced by buildArgs(postDrainState). The buildArgs callback receives the post-detach WorkflowStreamState as its only argument.
Explicit Continue-As-New recipe for custom parameters
To pass custom Continue-As-New parameters such as a different task queue or custom publisher TTL, use the explicit recipe: call stream.DetachPollers(), await workflow.AllHandlersFinished(ctx), snapshot the state with stream.GetState(publisherTTL), set options on the context with workflow.WithWorkflowTaskQueue, then build the Continue-As-New error yourself with workflow.NewContinueAsNewError.
Long-running workflow stream state size with External Storage
The carried WorkflowStreamState includes the entire in-memory log of the previous run. Streams that carry large items can hit Temporal's per-payload size limit at the rollover. Offload the bytes via External Storage so each item is a small reference rather than the full payload, and combine that with stream.Truncate(upToOffset) to keep the carried log itself small.
NewContinueAsNewError default publisher TTL
stream.NewContinueAsNewError(...) snapshots with a 15-minute default publisher TTL. This value controls how long entries are kept for deduplication before the Continue-As-New rollover.
Entity Workflow: Continue-As-New to prevent unbounded history growth
Use Continue-As-New periodically to prevent unbounded history growth. Check isContinueAsNewSuggested() to determine when to continue. Always call Continue-As-New from the main Workflow method, never from handlers.
Entity Workflow: State passing across Continue-As-New
Carry the current entity state in the Continue-As-New input so it is not reset on the new run. The state field in the input object is unset for the original caller and populated only on continuation; the new run restores it before processing further operations.
Entity Workflow pitfall: 2 MB payload limit on Continue-As-New input
State passed to Continue-As-New is subject to the same 2 MB blob size limit as Workflow inputs. Use external storage for large state.
Entity Workflow pitfall: hardcoded counter vs isContinueAsNewSuggested
Use isContinueAsNewSuggested() which accounts for actual history size rather than a hardcoded counter. Hardcoded thresholds may be too aggressive or too lenient.
Entity Workflow best practice: version carefully
Use Worker versioning for Workflow code changes to ensure compatibility across entity instances.
Entity Workflow best practice: monitor history size
Alert when approaching the Continue-As-New threshold to proactively manage history growth.