Versioned worker configuration example
var options = new TemporalWorkerOptions("my-task-queue")
{
DeploymentOptions = new WorkerDeploymentOptions(
new WorkerDeploymentVersion("my-app", "1.0"),
useWorkerVersioning: true),
};
options.AddWorkflow<VersionedGreetingWorkflow>();
options.AddAllActivities(typeof(GreetingActivities), null);
using var worker = new TemporalWorker(client, options);
This example creates a versioned worker with deployment version "my-app" version "1.0" and enables worker versioning.
Patched example for activity replacement
Example of patching when replacing PrePatchActivity with PostPatchActivity:
```csharp
[Workflow]
public class MyWorkflow
{
[WorkflowRun]
public async Task RunAsync()
{
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) });
}
}
}
```
DeprecatePatch example
Example of deprecating a patch:
```csharp
[Workflow]
public class MyWorkflow
{
[WorkflowRun]
public async Task RunAsync()
{
Workflow.DeprecatePatch("my-patch")
this.result = await Workflow.ExecuteActivityAsync(
(MyActivities a) => a.PostPatchActivity(),
new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) });
}
}
```
When to remove a patch completely
A patch can be safely removed and replaced with just the new code once all Workflows labeled with that patch or earlier have left retention. At this point, the Workflow Definition can be simplified to execute only the new activity without any conditional logic.
Workflow cutover alternative to patching
As an alternative to patching, you can create an entirely new Workflow by duplicating the Workflow Definition with a different name (e.g., SayHelloWorkflowV2) and registering both with Workers. The downside is that it requires code duplication, updating commands to start the Workflow, and doesn't provide versioning for still-running Workflows—it's essentially just a cutover rather than true versioning.
DeprecatePatch function and behavior
DeprecatePatch is called after all Workflows started with the original code have left retention. Deprecated patches add a marker to the Event History but won't cause a replay failure when the Workflow code doesn't produce the marker. This allows old Workers still running original code to safely use the patched branch if they pick up Workflow histories generated by new code.
UpdateWorkerBuildIdCompatibility API for adding new Build ID
The client.UpdateWorkerBuildIdCompatibility method is used to tell the Task Queue about a Worker's Build ID. To add a Build ID as the sole version in a new version set that becomes the default for the queue, use the BuildIDOpAddNewIDInNewDefaultSet operation. Example: err := client.UpdateWorkerBuildIdCompatibility(ctx, &client.UpdateWorkerBuildIdCompatibilityOptions{ TaskQueue: "your_task_queue_name", Operation: &client.BuildIDOpAddNewIDInNewDefaultSet{ BuildID: "deadbeef", }, }). New Workflows execute on Workers with this Build ID, and existing ones continue to process with appropriately compatible Workers.
Versioning behavior configuration rules
A versioning behavior applies only to a Worker that has versioning enabled. Setting DefaultVersioningBehavior without UseVersioning is an error. With versioning enabled and no default set, a Workflow that does not set its own behavior fails at registration time.
Enable Worker Versioning
Enable Worker versioning in worker.Options by setting DeploymentOptions with UseVersioning: true and providing a Worker Deployment Version with DeploymentName and BuildID. Then set a versioning behavior on each Workflow using RegisterWorkflowWithOptions() with workflow.RegisterOptions, or set DefaultVersioningBehavior in worker.DeploymentOptions for all Workflows on the Worker.
Versioned Worker example
Example of creating a versioned Worker in Go:
w := worker.New(c, "my-task-queue", worker.Options{
DeploymentOptions: worker.DeploymentOptions{
UseVersioning: true,
Version: worker.WorkerDeploymentVersion{
DeploymentName: "my-app",
BuildID: "1.0",
},
},
})
w.RegisterWorkflowWithOptions(HelloWorkflow, workflow.RegisterOptions{
VersioningBehavior: workflow.VersioningBehaviorPinned,
})
This example shows how to enable versioning with a deployment name and build ID, and set a specific versioning behavior for a Workflow.
Versioning behavior requirement for Lambda Workflows
Each Workflow registered with a Lambda Worker must have a versioning behavior specified, either AutoUpgrade or Pinned. Set it per-Workflow at registration time using workflow.RegisterOptions{VersioningBehavior: workflow.VersioningBehaviorPinned}, or set a worker-level default with DefaultVersioningBehavior in DeploymentOptions.
Every Workflow needs a versioning behavior on Cloud Run Worker
Every Workflow on a Cloud Run Worker requires a versioning behavior, either VersioningBehaviorPinned or VersioningBehaviorAutoUpgrade. Set it per Workflow at registration with RegisterWorkflowWithOptions, or set DefaultVersioningBehavior in DeploymentOptions to cover every Workflow on the Worker. If a Version is set and neither behavior is specified, registration panics with 'workflow type does not have a versioning behavior'.
Workflow cutover alternative: creating new Workflow versions
Instead of using patching, you can avoid determinism errors by creating an entirely new Workflow when making incompatible changes. Copy the original Workflow Definition function with a different name and register both with Workers:
func PizzaWorkflow(ctx workflow.Context, order PizzaOrder) (OrderConfirmation, error) {
// original code
}
func PizzaWorkflowV2(ctx workflow.Context, order PizzaOrder) (OrderConfirmation, error) {
// updated code
}
Register both:
w.RegisterWorkflow(pizza.PizzaWorkflow)
w.RegisterWorkflow(pizza.PizzaWorkflowV2)
The function name must start with an uppercase letter. The tradeoff is code duplication and needing to update all Workflow startup commands, and this method does not version in-progress Workflows.
GetVersion example: replacing ActivityA with ActivityC
To patch a Workflow that replaces ActivityA with ActivityC, use:
v := workflow.GetVersion(ctx, "Step1", workflow.DefaultVersion, 1)
if v == workflow.DefaultVersion {
err = workflow.ExecuteActivity(ctx, ActivityA, data).Get(ctx, &result1)
} else {
err = workflow.ExecuteActivity(ctx, ActivityC, data).Get(ctx, &result1)
}
if err != nil {
return "", err
}
For new Workflow Executions, GetVersion returns 1. For existing Workflow Executions that passed this call before it was introduced, it returns DefaultVersion.
Multiple GetVersion calls and code branch management
Multiple GetVersion() calls can be added within a single Workflow. However, managing this can become challenging with many long-running Workflows, as code branches accumulate over time. To manage this complexity, gradually deprecate older Workflow versions once you confirm no open Workflow Executions are based on those versions.
List Filter syntax for finding running Workflows with specific versions
Use List Filter syntax to find running Workflow Executions with a specific TemporalChangeVersion:
WorkflowType = "PizzaWorkflow"
AND ExecutionStatus = "Running"
AND TemporalChangeVersion="ChangedNotificationActivityType-1"
The number at the end represents the version number. Workflow Executions that were started before GetVersion was added to the code won't have the associated Marker in their Event History, so use:
WorkflowType = "PizzaWorkflow"
AND ExecutionStatus = "Running"
AND TemporalChangeVersion IS NULL
workflow.GetVersion() patch structure and parameters
workflow.GetVersion() creates a logical branch in a Workflow for a specific change. It takes four parameters: context (ctx), a string changeID that uniquely identifies the change, minSupported version (starting with workflow.DefaultVersion or a specific version number), and maxSupported version number. When GetVersion() is first run for a new Workflow Execution, it records a marker in the Event History. All future calls to GetVersion with the same changeID return the same version number.
Reusing changeID after removing GetVersion
Once a GetVersion() call is completely removed after ensuring all executions with older versions have left retention, that changeID cannot be reused. If you need to make future changes to the same part of the Workflow, use a different changeID such as "Step1-fix2" and start minSupported from workflow.DefaultVersion again:
v := workflow.GetVersion(ctx, "Step1-fix2", workflow.DefaultVersion, 1)
if v == workflow.DefaultVersion {
err = workflow.ExecuteActivity(ctx, ActivityD, data).Get(ctx, &result1)
} else {
err = workflow.ExecuteActivity(ctx, ActivityE, data).Get(ctx, &result1)
}
Deprecating old GetVersion branches after retention
Once all Workflow Executions prior to a specific version have left retention, minSupported can be updated to remove support for older versions. For example, change minSupported from DefaultVersion to 1:
v := workflow.GetVersion(ctx, "Step1", 1, 2)
if v == 1 {
err = workflow.ExecuteActivity(ctx, ActivityC, data).Get(ctx, &result1)
} else {
err = workflow.ExecuteActivity(ctx, ActivityD, data).Get(ctx, &result1)
}
If an older Workflow Execution is replayed, it fails because the minimum expected version is 1.
GetVersion multi-step patching with maxSupported increments
When making additional changes like replacing ActivityC with ActivityD, increment maxSupported from 1 to 2:
v := workflow.GetVersion(ctx, "Step1", workflow.DefaultVersion, 2)
if v == workflow.DefaultVersion {
err = workflow.ExecuteActivity(ctx, ActivityA, data).Get(ctx, &result1)
} else if v == 1 {
err = workflow.ExecuteActivity(ctx, ActivityC, data).Get(ctx, &result1)
} else {
err = workflow.ExecuteActivity(ctx, ActivityD, data).Get(ctx, &result1)
}
Workflows that passed this GetVersion call with maxSupported=1 return 1. New Workflows return 2.
Runtime check prevents incompatible Workflow changes
The Temporal Go SDK performs runtime checks to prevent obvious incompatible changes. Adding, removing, or reordering any of these methods without versioning triggers a nondeterminism error: workflow.ExecuteActivity(), workflow.ExecuteChildWorkflow(), workflow.NewTimer(), workflow.RequestCancelWorkflow(), workflow.SideEffect(), workflow.SignalExternalWorkflow(), workflow.Sleep(). The runtime check is not thorough—it does not validate Activity input arguments or Timer duration, for example. Replay Testing should be used when making revisions to ensure complete compatibility.
Preserving GetVersion call after removing all branches
After all old Workflow Executions have left retention, preserve the first GetVersion() call with matching minSupported and maxSupported:
_ := workflow.GetVersion(ctx, "Step1", 2, 2)
err = workflow.ExecuteActivity(ctx, ActivityD, data).Get(ctx, &result1)
This ensures: (1) if any Workflow Execution for an older version is still running, it fails rather than proceeding incorrectly, and (2) if future changes to Step1 are needed, you only update maxSupported. All subsequent calls to GetVersion() with the same changeID can be safely removed, but preserve the first call.
Workflow versioning requirement for long-running executions
Temporal Workflow Executions can run for extended periods such as months or years. When making changes to a Workflow Definition during execution, versioning methods must be used to maintain determinism and gracefully update running Workflows. Non-deterministic work like API calls and database queries should be placed in Activities, which Temporal retries reliably.
Add Build ID to task queue as new default set using updateWorkerBuildIdCompatability
Use workflowClient.updateWorkerBuildIdCompatability("task_queue_name", BuildIdOperation.newIdInNewDefaultSet("buildId")) to add a Build ID to the task queue as the sole version in a new version set, which becomes the default. New workflows will execute on Workers with this Build ID.
New Worker with versioning enabled does not receive tasks until assignment rules are set
When you start a Worker with versioning enabled (setUseBuildIdForVersioning(true)), it will not receive any tasks until you set up assignment rules on the task queue.
Add Build ID to existing compatible set using updateWorkerBuildIdCompatability
Use workflowClient.updateWorkerBuildIdCompatability("task_queue_name", BuildIdOperation.newCompatibleVersion("newBuildId", "existingBuildId")) to add a Build ID to an existing compatible set containing another Build ID and mark it as the new default for that set.
Worker Versioning is deprecated in Java SDK
The Worker Versioning API documented in this section is deprecated. For current implementation, refer to the Worker Versioning production deployment documentation instead of this legacy API.
Promote entire Build ID set to default queue set using updateWorkerBuildIdCompatability
Use workflowClient.updateWorkerBuildIdCompatability("task_queue_name", BuildIdOperation.promoteSetByBuildId("buildId")) to promote an entire compatible set to become the default set for the task queue. New workflows will start using that set's default Build ID.
Set versioning behavior on Workflow implementations
Annotate the Workflow method with @WorkflowVersioningBehavior(VersioningBehavior.PINNED) or @WorkflowVersioningBehavior(VersioningBehavior.AUTO_UPGRADE), or set a default for the whole Worker with setDefaultVersioningBehavior(). The annotation belongs on the implementation, not the interface. A versioning behavior applies only to a Worker that has versioning enabled.
Example: Create a versioned Worker
WorkerOptions options =
WorkerOptions.newBuilder()
.setDeploymentOptions(
WorkerDeploymentOptions.newBuilder()
.setVersion(new WorkerDeploymentVersion("my-app", "1.0"))
.setUseVersioning(true)
.build())
.build();
Worker worker = factory.newWorker("my-task-queue", options);
worker.registerWorkflowImplementationTypes(VersionedGreetingWorkflowImpl.class);
worker.registerActivitiesImplementations(new GreetingActivitiesImpl());
Enable versioning with WorkerDeploymentOptions
Set a Worker Deployment Version and enable versioning by passing WorkerOptions with WorkerDeploymentOptions to newWorker(). Use WorkerDeploymentOptions.newBuilder().setVersion(new WorkerDeploymentVersion("my-app", "1.0")).setUseVersioning(true).build() to enable versioning.
Workflow versioning behavior annotation example
@Override
@WorkflowVersioningBehavior(VersioningBehavior.PINNED)
public String getGreeting(String name) {
logger.info("SampleWorkflow started for {}", name);
String result = activities.createGreeting(name);
logger.info("SampleWorkflow completed with {}", result);
return result;
}
This example shows how to apply the @WorkflowVersioningBehavior annotation with VersioningBehavior.PINNED to a Workflow method on a Lambda Worker.
Use Workflow.getVersion for code changes
Use Workflow.getVersion when making any changes to the Workflow code to avoid deployment of updated Workflow code interfering with already-running Workflows.
Workflow.getVersion() method signature and behavior
Workflow.getVersion() takes three parameters: a change ID string that uniquely identifies this change, a minimum version number (typically Workflow.DEFAULT_VERSION which is 0), and a maximum version number. When called on a new Workflow Execution, it records a marker in the Event History and returns the maximum version number. On replays of existing Workflow Executions, it returns the version number from the recorded marker.
Remove patching code after retention period
After all Workflow Executions from the previous version have left retention, you can remove the branching code added by getVersion. However, keep the getVersion call itself to ensure that any attempt to replay history for a different version fails. The getVersion call can be removed later when there is no possibility of replaying history.
Workflow.getVersion() creates a patch for code changes
Workflow.getVersion() is used to create a logical branch in a Workflow for a specific change. It records a marker in the Event History so that all future calls to getVersion with the same change ID will always return the same version number, allowing in-progress Workflow Executions to continue with their original code while new executions use the updated code.
Enable automatic version search attributes in WorkflowImplementationOptions
To enable automatic TemporalChangeVersion upserts in Java SDK v1.30.0+, create WorkflowImplementationOptions with setEnableUpsertVersionSearchAttributes(true) and pass it to worker.registerWorkflowImplementationTypes() along with the Workflow implementation class.
Workflow code must be deterministic
Workflow code in Temporal must be deterministic to support replay of event history. Non-deterministic changes to Workflow code that would affect replay will cause nondeterminism errors. This requirement applies to Workflow orchestration logic, not to Activities, which can perform non-deterministic work like API calls and database queries.
Manually set TemporalChangeVersion search attribute
If using Java SDK earlier than v1.30.0 or preferring manual control, you can upsert the TemporalChangeVersion Search Attribute manually. Define SearchAttributeKey.forKeywordList('TemporalChangeVersion'), then after calling Workflow.getVersion(), use Workflow.upsertTypedSearchAttributes() with a list of '<Change ID>-<Version>' strings to set the attribute.
Workflow cutover by creating new Workflow type
As an alternative to patching, you can create a new Workflow with a different name to avoid determinism errors when making changes. This involves copying the Workflow Definition to a new class with a different interface, giving it a new name like V2, and registering both Workflow types with the Worker. The downside is code duplication and the need to update any commands that start the Workflow. This method does not provide versioning for still-running Workflows.
Use replay testing to verify patching safety
Replay Testing should be incorporated into your testing suite to determine whether your Workflow needs a patch or to verify that you have patched it successfully. This ensures that your Workflow changes do not introduce nondeterminism errors during replay.
Patching example: adding a new Activity with getVersion
When adding a new Activity to an existing Workflow, use Workflow.getVersion() to conditionally execute it. For new Workflow Executions, the new version runs the new Activity code path. For existing Workflow Executions replaying their history, the old version skips the new Activity to avoid nondeterminism. Example: int version = Workflow.getVersion("checksumAdded", Workflow.DEFAULT_VERSION, 1); if (version == Workflow.DEFAULT_VERSION) { /* old code */ } else { /* new code with checksum */ }
Workflow cutover as versioning alternative
A Workflow cutover involves creating a completely new Workflow Definition with a different name and registering both names with Workers. For example, duplicate MyWorkflow as MyWorkflowV2 and register both types. The downside is that this requires duplicating code and updating any commands used to start the Workflow. Unlike Patching, this method provides no way to version still-running Workflows and is essentially just a cutover.
Runtime nondeterminism checks in PHP SDK
The Temporal PHP SDK performs runtime checks to prevent obvious incompatible changes. Adding, removing, or reordering any of these methods without Versioning triggers a nondeterminism error: workflow.ExecuteActivity(), workflow.ExecuteChildWorkflow(), workflow.NewTimer(), workflow.RequestCancelWorkflow(), workflow.SideEffect(), workflow.SignalExternalWorkflow(), workflow.Sleep(). However, these checks are not thorough; they do not check Activity input arguments or Timer duration. You should incorporate Replay Testing when making revisions.
Removing old version code in PHP Workflow patching
After all Workflow Executions prior to a version have left retention, you can remove that version's code. First, update minSupported to the next version while keeping the old version code:
```php
#[WorkflowMethod]
public function runAsync()
{
$version = yield Workflow::getVersion('Step 1', minSupported: 1, maxSupported: 2);
$result = match($version) {
1 => yield $this->activity->postPatchActivity(),
2 => yield $this->activity->anotherPatchActivity(),
};
}
```
Then, after all Workflow Executions for version 1 leave retention, remove version 1 entirely:
```php
#[WorkflowMethod]
public function runAsync()
{
$version = yield Workflow::getVersion('Step 1', minSupported: 2, maxSupported: 2);
$result = yield $this->activity->anotherPatchActivity();
}
```
If an older version history is replayed on this code, it fails because the minimum expected version is 2.
Getversion example with single patch in PHP
Example showing how to use Workflow::getVersion() to patch a Workflow when replacing prePatchActivity with postPatchActivity:
```php
#[WorkflowInterface]
class MyWorkflow
{
// ...
#[WorkflowMethod]
public function runAsync()
{
$version = yield Workflow::getVersion('Step 1', Workflow::DEFAULT_VERSION, 1);
$result = $version === Workflow::DEFAULT_VERSION
? yield $this->activity->prePatchActivity()
: yield $this->activity->postPatchActivity();
}
}
```
This patch uses the change ID 'Step 1' and returns either DEFAULT_VERSION for existing executions or 1 for new executions.
Workflow versioning methods in PHP SDK
The Temporal PHP SDK provides two primary versioning methods for updating Workflow Definitions: Worker Versioning and Versioning with Patching. Worker Versioning allows you to tag Workers and programmatically roll them out in Deployment Versions so old Workers run old code paths and new Workers run new code paths. Versioning with Patching works by adding branches to code tied to specific revisions, applying changes to new Workflow Executions while avoiding disruptive changes to in-progress ones.
Determinism requirement for Workflow code
Temporal requires that Workflow code is deterministic. If you make a change to Workflow code that would cause non-deterministic behavior on Replay, you must use Versioning methods to gracefully update running Workflows. Non-deterministic work such as API calls and database queries should be placed in Activities, which Temporal retries reliably.
Workflow::getVersion() for patching in PHP SDK
The Workflow::getVersion() method is used to patch Workflow code. It takes a change ID string, a minSupported version, and a maxSupported version. When getVersion() runs for a new Workflow Execution, it records a marker in the Event History so that all future calls with the same change ID on that Workflow Execution will always return the given version number. For existing Workflow Executions that passed this call before it was introduced, it returns DEFAULT_VERSION.
Multiple getVersion patches in PHP Workflow
Example showing how to handle multiple patches by incrementing maxSupported:
```php
#[WorkflowInterface]
class MyWorkflow
{
// ...
#[WorkflowMethod]
public function runAsync()
{
$version = yield Workflow::getVersion('Step 1', Workflow::DEFAULT_VERSION, maxSupported: 2);
$result = match($version) {
Workflow::DEFAULT_VERSION => yield $this->activity->prePatchActivity(),
1 => yield $this->activity->postPatchActivity(),
2 => yield $this->activity->anotherPatchActivity(),
};
}
}
```
When maxSupported is increased from 1 to 2, Workflows that already passed this getVersion() call return DEFAULT_VERSION, those run with maxSupported=1 return 1, and new Workflows return 2.
Run a versioned Worker in Python
To run a versioned Worker in Python, set a Worker Deployment Version and enable versioning in `deployment_config`, then set a default versioning behavior for Workflows. Pass `deployment_config=WorkerDeploymentConfig(version=WorkerDeploymentVersion(deployment_name="my-app", build_id="1.0"), use_worker_versioning=True, default_versioning_behavior=VersioningBehavior.PINNED)` to the Worker constructor. Alternatively, set versioning behavior per Workflow by passing `versioning_behavior` to @workflow.defn.
Versioned Worker example in Python
Example of creating a versioned Worker in Python:
worker = Worker(
client,
task_queue="my-task-queue",
workflows=[HelloWorkflow],
activities=[some_activity],
deployment_config=WorkerDeploymentConfig(
version=WorkerDeploymentVersion(
deployment_name="my-app",
build_id="1.0",
),
use_worker_versioning=True,
default_versioning_behavior=VersioningBehavior.PINNED,
),
)
Cloud Run Worker per-workflow versioning behavior example
from temporalio import workflow
from temporalio.common import VersioningBehavior
@workflow.defn(versioning_behavior=VersioningBehavior.PINNED)
class MyWorkflow:
@workflow.run
async def run(self, name: str) -> str:
...
Cloud Run Worker versioning behavior configuration
Every Workflow on a Cloud Run Worker needs a versioning behavior set to either PINNED or AUTO_UPGRADE. You can set default_versioning_behavior on the WorkerDeploymentConfig to apply the same behavior to all Workflows, or pass versioning_behavior to individual @workflow.defn decorators to set behavior per Workflow.
Example workflow with PINNED versioning behavior
Example workflow definition with versioning behavior: from temporalio import workflow; from temporalio.common import VersioningBehavior; @workflow.defn(versioning_behavior=VersioningBehavior.PINNED); class MyWorkflow: @workflow.run; async def run(self, input: str) -> str: ...
WorkerDeploymentVersion identifies worker deployment and build ID
The WorkerDeploymentVersion identifies the Worker Deployment and Build ID for a Worker. The deployment name groups related Workers across versions, and the Build Id identifies a specific release of your Worker code. Worker Versioning is required for Serverless Workers.
Workflows require versioning behavior PINNED or AUTO_UPGRADE
Each Workflow must have a versioning behavior, either PINNED or AUTO_UPGRADE. Set it per-Workflow in the @workflow.defn decorator with the versioning_behavior parameter, or set a worker-level default with default_versioning_behavior in the worker config.
When to use Continue-As-New
Use Continue-As-New when your Workflow might encounter degraded performance or Event History Limits. Call workflow.info().is_continue_as_new_suggested() to check if it is time to Continue-As-New.
Continue-As-New function in Python SDK
Call the workflow.continue_as_new() function with the same type as your Workflow parameters. This stops the Workflow right away and starts a new one. The new Workflow Execution will be passed the parameters you provide to continue_as_new().
Continue-As-New example in Python
Example of calling Continue-As-New in Python:
workflow.continue_as_new(
ClusterManagerInput(
state=self.state,
test_continue_as_new=input.test_continue_as_new,
)
)
This passes the current state and other parameters to the new Workflow Execution.