Click or drag to resize

Java differences

Our initial .NET Cadence client implementation was modeled more closely to the Uber Cadence Go client and not the Java client. We discussed this with Maxim and he convinced us that we really should use Java as the standard because it's better and it's hard to argue with better. So we did an extensive top-to-bottom refactoring and ended up with something we're pretty happy with.

We did want to embrace some .NET capabilies like async/await that are not present in the Java world. We also made a few changes that align with our underlying implementation (which pxoxies a Cadence Go client). So there are some differences, but we tried pretty hard to keep the spirit of the Java client alive. This page outlines the more important differences.


The .NET Cadence client follows the modern C# task conventions by taking advantage of async/await. This can dramatically reduce the number of threads required to execute workflows and activities improving scalability. As a consequence, we've appended Async to many our method names, following the .NET convention. We have kept the root Java names though, where they make sense.


We followed the .NET convention and renamed the API methods to be PascalCase.


.NET langauges like C# and Visual Basic support properties as first class language elements. The .NET client exposes properties rather than duplicating the Java getXXX()/setXXX() convention.

native Types

We use native .NET types like TimeSpan and DateTime rather than porting the corresponding Java types.


We've combined that capabilities of the Java IServiceClient and WorkflowClient types into our CadenceClient class. This class is disposable, following .NET conventions and we added CadenceSettings which is used to configure a Cadence client. These settings can be easily deserialized from JSON or YAML which is handy when loading your workflow service configuration from a file (e.g. a Kubernetes pod config).


The Java client does not require that workflow and activity implementations be registered before use (which is nice). The .NET client follows the Go client convention and requires registration.

ambient globals

The Java client uses thread-local storage to expose the static Workflow class to threads executing workflows and Activity to threads executing activities. These classes return values and perform operations for the workflow or activity associated with the thread. For workflows we use AsyncLocalT internally to implement the staticCurrent property. This will be set when your workflow code is invoked and will be available within your workflow class as well as any code your workflow calls. This works because workflows are prohibited from starting additional threads.

Note Note

Code within your workflow class can also reference this state via this shortcut: Workflow

We did not implement a similar ambient property for activities because activities are allowed to spin up threads and AsyncLocalT won't work as expected for activities that create threads.

So instead we require that workflow implementations derive from WorkflowBase and activity implementations derive from ActivityBase. WorkflowBase exposes the Workflow property and ActivityBase exposes the Activity property. These perform the same functions as the corresponding Java types and will look pretty much the same in your workflow code to boot.

activity instances

The Java client creates only a single instance of an activity class for each activity implementation and requires that developers take care to ensure that their activity code is thread-safe. The .NET implementation constructs a distinct activity class instance for every activity invoked. This is a consequence of exposing the Activity property with per-actvity invocation properties and methods. We also believe this is nicer.

optional arguments

C# and Visual Basic support optional arguments. Many of our APIs take advantage of this to simplify the API surface area.

nullable types

Java and Go types like strings and classes aren't implicitly nullable whereas these are nullable in .NET languages. Java and Go deal with this by using wrapper types in some situations. The .NET client uses the .NET capabilities to simplify some of these types.

default domain

The .NET client can optionally be configured with a default Cadence domain and this domain will be assumed when the domain isn't specified for an operation. We added this as a convienence for the common situation where all workflow service operations are constrained to a single domain.

unit testing

The .NET client doesn't currently provide an in-memory implementation that can be used for intensive workflow unit testing. This is something we'll be adding in the future. In the meantime, we do have the CadenceFixture which can be used to run unit tests against a real Cadence service running locally as a Docker container.

workflow queues

I suspect that the .NET WorkflowQueueT class is less capable than the Java equivalant, but the Java class isn't really documented. The .NET class is designed for just two scenarios:

  • Signal processing: Signal methods can write items to a queue for the workflow method to read.

  • Workflow queues: Workflow methods and read and write items to queues.

.NET queues cannot be shared across different workflows or between a workflow and its activities.

data converters

The Java client supports custom data converters for specific workflows and activities as well as for the client. .NET currently supports custom converters at the client level. This is something we may add in the future.

See Also