Java SDK developer's guide - Foundations
The Foundations section of the Temporal Developer's guide covers the minimum set of concepts and implementation details needed to build and run a Temporal ApplicationWhat is a Temporal Application
A Temporal Application is a set of Workflow Executions.
Learn more—that is, all the relevant steps to start a Workflow Execution that executes an Activity.
This guide is a work in progress. Some sections may be incomplete or missing for some languages. Information may change at any time.
If you can't find what you are looking for in the Developer's guide, it could be in older docs for SDKs.
In this section you can find the following:
- Run a development ClusterHow to install Temporal CLI and run a development server
undefined
Learn more - Install your SDKHow to install a Temporal SDK
A Temporal SDK provides a framework for Temporal Application development.
Learn more - Connect to a dev ClusterHow to create a Temporal Client in Java
To initialize a Workflow Client, create an instance of a `WorkflowClient`, create a client-side `WorkflowStub`, and then call a Workflow method (annotated with the `@WorkflowMethod` annotation).
Learn more - Develop a WorkflowHow to develop a Workflow Definition in Java
In the Temporal Java SDK programming model, a Workflow is a class which implements a Workflow interface.
Learn more - Develop an ActivityHow to develop a basic Activity
One of the primary things that Workflows do is orchestrate the execution of Activities.
Learn more - Start an Activity ExecutionHow to start an Activity Execution
Calls to spawn Activity Executions are written within a Workflow Definition.
Learn more - Run a dev WorkerHow to develop a Worker Program in Java
Use the `newWorker` method on an instance of a `WorkerFactory` to create a new Worker in Java.
Learn more - Start a Workflow ExecutionHow to spawn a Workflow Execution in Java
Use `WorkflowStub` to start a Workflow Execution from within a Client, and `ExternalWorkflowStub` to start a different Workflow Execution from within a Workflow.
Learn more
Run a development server
This section describes how to install the Temporal CLI and run a development Cluster. The local development Cluster comes packaged with the Temporal Web UI.
For information on deploying and running a production Cluster, see the Cluster deployment guide, or sign up for Temporal Cloud and let us run your production Cluster for you.
Temporal CLI is a tool for interacting with a Temporal Cluster from the command line and it includes a distribution of the Temporal Server and Web UI. This local development Cluster runs as a single process with zero runtime dependencies and it supports persistence to disk and in-memory mode through SQLite.
Install the Temporal CLI
Choose one of the following install methods to install the Temporal CLI.
- macOS
- Linux
- Windows
Install the Temporal CLI with Homebrew.
brew install temporal
Install the Temporal CLI with cURL.
curl -sSf https://temporal.download/cli.sh | sh
Install the Temporal CLI from CDN.
- Select the platform and architecture needed.
- Extract the downloaded archive.
- Add the
temporal
binary to your PATH.
Install the Temporal CLI with cURL.
curl -sSf https://temporal.download/cli.sh | sh
Install the Temporal CLI from CDN.
- Select the platform and architecture needed.
- Extract the downloaded archive.
- Add the
temporal
binary to your PATH.
- Install the Temporal CLI from CDN.
- Select the platform and architecture needed and download the binary.
- Extract the downloaded archive.
- Add the
temporal.exe
binary to your PATH.
Start the Temporal Development Server
Start the Temporal Development Server by using the server start-dev
command.
temporal server start-dev
This command automatically starts the Web UI, creates the default Namespace, and uses an in-memory database.
The Temporal Server should be available on localhost:7233
and the Temporal Web UI should be accessible at http://localhost:8233
.
The server's startup configuration can be customized using command line options. For a full list of options, run:
temporal server start-dev --help
Install a Temporal SDK
A Temporal SDKWhat is a Temporal SDK?
A Temporal SDK is a language-specific library that offers APIs to construct and use a Temporal Client to communicate with a Temporal Cluster, develop Workflow Definitions, and develop Worker Programs.
Learn more provides a framework for Temporal ApplicationWhat is a Temporal Application
A Temporal Application is a set of Workflow Executions.
Learn more development.
An SDK provides you with the following:
- A Temporal ClientWhat is a Temporal Client
A Temporal Client, provided by a Temporal SDK, provides a set of APIs to communicate with a Temporal Cluster.
Learn more to communicate with a Temporal ClusterWhat is a Temporal Cluster?
A Temporal Cluster is the Temporal Server paired with persistence.
Learn more. - APIs to develop WorkflowsWhat is a Workflow?
In day-to-day conversations, the term "Workflow" frequently denotes either a Workflow Type, a Workflow Definition, or a Workflow Execution.
Learn more. - APIs to create and manage Worker ProcessesWhat is a Worker?
In day-to-day conversations, the term Worker is used to denote both a Worker Program and a Worker Process. Temporal documentation aims to be explicit and differentiate between them.
Learn more. - APIs to author ActivitiesWhat is an Activity Definition?
An Activity Definition is the code that defines the constraints of an Activity Task Execution.
Learn more.
Add the Temporal Java SDK to your project as a dependency:
<dependency>
<groupId>io.temporal</groupId>
<artifactId>temporal-sdk</artifactId>
<version>1.17.0</version>
</dependency>
implementation 'io.temporal:temporal-sdk:1.17.0'
Other:
Additional scripts for each SDK version are available here: https://search.maven.org/artifact/io.temporal/temporal-sdk. Select an SDK version to see available scripts.
API reference
The Temporal Java SDK API reference is published on javadoc.io.
- Short link:
t.mp/java-api
Code samples
You can find a complete list of executable code samples in Temporal's GitHub repository.
Additionally, several of the Tutorials are backed by a fully executable template application.
- Java samples library
- Hello world application template in Java: Provides a quick-start development app for users. Works in conjunction with the "Hello World!" from scratch tutorial in Java.
- Money transfer application template in Java: Provides a quick-start development app for users. It demonstrates a basic "money transfer" Workflow Definition and works in conjunction with the Run your first app tutorial in Java.
- Subscription-style Workflow Definition in Java: Demonstrates some of the patterns that could be implemented for a subscription-style business process.
Connect to a dev Cluster
A Temporal ClientWhat is a Temporal Client
A Temporal Client, provided by a Temporal SDK, provides a set of APIs to communicate with a Temporal Cluster.
Learn more enables you to communicate with the Temporal ClusterWhat is a Temporal Cluster?
A Temporal Cluster is the Temporal Server paired with persistence.
Learn more.
Communication with a Temporal Cluster includes, but isn't limited to, the following:
- Starting Workflow Executions.
- Sending Signals to Workflow Executions.
- Sending Queries to Workflow Executions.
- Getting the results of a Workflow Execution.
- Providing an Activity Task Token.
A Temporal Client cannot be initialized and used inside a Workflow. However, it is acceptable and common to use a Temporal Client inside an Activity to communicate with a Temporal Cluster.
When you are running a Cluster locally (such as Temporalite), the number of connection options you must provide is minimal.
Many SDKs default to the local host or IP address and port that Temporalite and Docker Compose serve (127.0.0.1:7233
).
To initialize a Workflow Client, create an instance of a WorkflowClient
, create a client-side WorkflowStub
, and then call a Workflow method (annotated with @WorkflowMethod
).
To start a Workflow Execution, your Temporal Server must be running, and your front-end service must be accepting gRPC calls.
To establish a connection with the front-end service, use WorkflowServiceStubs
.
WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
You can provide WorkflowServiceStubsOptions
to override the default values for the gRPC calls.
For example, the default front-end service gRPC address is set to 127.0.0.1:7233
, where 7233
is the default port for the Temporal Frontend Service.
If your server is running on a different host or port from the default, you can set it as shown in the following example.
WorkflowServiceStubs service = WorkflowServiceStubs.newInstance(
WorkflowServiceStubsOptions.newBuilder()
.setTarget(TARGET_ENDPOINT)
.build());
After the connection to the Temporal Frontend Service is established, create a Client for the service stub. The Workflow Client helps with client-side APIs and is required by Workers.
Create an instance of a WorkflowClient
for the Workflow service stub, and use WorkflowClientOptions
to set options for the Workflow Client.
The following example shows how to create a WorkflowClient
instance called "client" for the WorkflowServiceStubs
"service" that we created in the previous example, and set Namespace
option for the WorkflowClient
.
WorkflowClient client = WorkflowClient.newInstance(
service,
WorkflowClientOptions.newBuilder()
.setNamespace(“Abc”)
.build());
For more information, see WorkflowClientOptions.
WorkflowService
and WorkflowClient
creation is a heavyweight operation, and will be resource-intensive if created each time you start a Workflow or send a Signal to it.
The recommended way is to create them once and reuse where possible.
With the Client defined, you can start interacting with the Temporal Frontend Service.
To initialize a Workflow in the Client, create a WorkflowStub
, and start the Workflow Execution with WorkflowClient.start()
.
Starting Workflows or sending Signals or Queries to Workflows from within a Client must be done using WorkflowStubs
.
WorkflowClient workflowClient = WorkflowClient.newInstance(service, clientOptions);
// Create a Workflow stub.
YourWorkflow workflow = workflowClient.newWorkflowStub(YourWorkflow.class);
// Start Workflow asynchronously and call its "yourWFMethod" Workflow method
WorkflowClient.start(workflow::yourWFMethod);
For more information, see the following:
- How to spawn a Workflow Execution in JavaHow to spawn a Workflow Execution in Java
Use `WorkflowStub` to start a Workflow Execution from within a Client, and `ExternalWorkflowStub` to start a different Workflow Execution from within a Workflow.
Learn more
Develop Workflows
Workflows are the fundamental unit of a Temporal Application, and it all starts with the development of a Workflow DefinitionWhat is a Workflow Definition?
A Workflow Definition is the code that defines the constraints of a Workflow Execution.
Learn more.
In the Temporal Java SDK programming model, a Workflow Definition comprises a Workflow interface annotated with @WorkflowInterface
and a Workflow implementation that implements the Workflow interface.
The Workflow interface is a Java interface and is annotated with @WorkflowInterface
.
Each Workflow interface must have only one method annotated with @WorkflowMethod
.
// Workflow interface
@WorkflowInterface
public interface YourWorkflow {
@WorkflowMethod
String yourWFMethod(Arguments args);
}
However, when using dynamic Workflows, do not specify a @WorkflowMethod
, and implement the DynamicWorkflow
directly in the Workflow implementation code.
The @WorkflowMethod
identifies the method that is the starting point of the Workflow Execution.
The Workflow Execution completes when this method completes.
You can create interface inheritance hierarchies to reuse components across other Workflow interfaces.
The interface inheritance approach does not apply to @WorkflowMethod
annotations.
A Workflow implementation implements a Workflow interface.
// Define the Workflow implementation which implements our getGreeting Workflow method.
public static class GreetingWorkflowImpl implements GreetingWorkflow {
...
}
}
To call Activities in your Workflow, call the Activity implementation.
Use ExternalWorkflowStub
to start or send Signals from within a Workflow to other running Workflow Executions.
You can also invoke other Workflows as Child Workflows with Workflow.newChildWorkflowStub()
or Workflow.newUntypedChildWorkflowStub()
within a Workflow Definition.
Use DynamicWorkflow
to implement Workflow Types dynamically.
Register a Workflow implementation type that extends DynamicWorkflow
to implement any Workflow Type that is not explicitly registered with the Worker.
The dynamic Workflow interface is implemented with the execute
method. This method takes in EncodedValues
that are inputs to the Workflow Execution.
These inputs can be specified by the Client when invoking the Workflow Execution.
public class MyDynamicWorkflow implements DynamicWorkflow {
@Override
public Object execute(EncodedValues args) {
}
}
Workflow parameters
Temporal Workflows may have any number of custom parameters. However, we strongly recommend that objects are used as parameters, so that the object's individual fields may be altered without breaking the signature of the Workflow. All Workflow Definition parameters must be serializable.
A method annotated with @WorkflowMethod
can have any number of parameters.
We recommend passing a single parameter that contains all the input fields to allow for adding fields in a backward-compatible manner.
Note that all inputs should be serializable by the default Jackson JSON Payload Converter.
You can create a custom object and pass it to the Workflow method, as shown in the following example.
//...
@WorkflowInterface
public interface YourWorkflow {
@WorkflowMethod
String yourWFMethod(CustomObj customobj);
// ...
}
Workflow return values
Workflow return values must also be serializable. Returning results, returning errors, or throwing exceptions is fairly idiomatic in each language that is supported. However, Temporal APIs that must be used to get the result of a Workflow Execution will only ever receive one of either the result or the error.
Workflow method arguments and return values must be serializable and deserializable using the provided DataConverter
.
The execute
method for DynamicWorkflow
can return type Object.
Ensure that your Client can handle an Object type return or is able to convert the Object type response.
Related references:
- Data ConverterWhat is a Data Converter?
A Data Converter is a Temporal SDK component that serializes and encodes data entering and exiting a Temporal Cluster.
Learn more - Java DataConverter reference: https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/common/converter/DataConverter.html
Workflow Type
Workflows have a Type that are referred to as the Workflow name.
The following examples demonstrate how to set a custom name for your Workflow Type.
The Workflow Type defaults to the short name of the Workflow interface.
In the following example, the Workflow Type defaults to NotifyUserAccounts
.
@WorkflowInterface
public interface NotifyUserAccounts {
@WorkflowMethod
void notify(String[] accountIds);
}
To overwrite this default naming and assign a custom Workflow Type, use the @WorkflowMethod
annotation with the name
parameter.
In the following example, the Workflow Type is set to Abc
.
@WorkflowInterface
public interface NotifyUserAccounts {
@WorkflowMethod(name = "Abc")
void notify(String[] accountIds);
}
When you set the Workflow Type this way, the value of the name
parameter does not have to start with an uppercase letter.
Workflow logic requirements
Workflow logic is constrained by deterministic execution requirementsWhat is a Workflow Definition?
A Workflow Definition is the code that defines the constraints of a Workflow Execution.
Learn more.
Therefore, each language is limited to the use of certain idiomatic techniques.
However, each Temporal SDK provides a set of APIs that can be used inside your Workflow to interact with external (to the Workflow) application code.
When defining Workflows using the Temporal Java SDK, the Workflow code must be written to execute effectively once and to completion.
The following constraints apply when writing Workflow Definitions:
- Do not use mutable global variables in your Workflow implementations. This will ensure that multiple Workflow instances are fully isolated.
- Your Workflow code must be deterministic.
Do not call non-deterministic functions (such as non-seeded random or
UUID.randomUUID()
) directly from the Workflow code. The Temporal SDK provides specific API for calling non-deterministic code in your Workflows. - Do not use programming language constructs that rely on system time.
For example, only use
Workflow.currentTimeMillis()
to get the current time inside a Workflow. - Do not use native Java
Thread
or any other multi-threaded classes likeThreadPoolExecutor
. UseAsync.function
orAsync.procedure
, provided by the Temporal SDK, to execute code asynchronously. - Do not use synchronization, locks, or other standard Java blocking concurrency-related classes besides those provided by the Workflow class.
There is no need for explicit synchronization because multi-threaded code inside a Workflow is executed one thread at a time and under a global lock.
- Call
Workflow.sleep
instead ofThread.sleep
. - Use
Promise
andCompletablePromise
instead ofFuture
andCompletableFuture
. - Use
WorkflowQueue
instead ofBlockingQueue
.
- Call
- Use
Workflow.getVersion
when making any changes to the Workflow code. Without this, any deployment of updated Workflow code might break already running Workflows. - Do not access configuration APIs directly from a Workflow because changes in the configuration might affect a Workflow Execution path. Pass it as an argument to a Workflow function or use an Activity to load it.
- Use
DynamicWorkflow
when you need a default Workflow that can handle all Workflow Types that are not registered with a Worker. A single implementation can implement a Workflow Type which by definition is dynamically loaded from some external source. All standardWorkflowOptions
and determinism rules apply to Dynamic Workflow implementations.
Java Workflow reference: https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/workflow/package-summary.html
Develop Activities
One of the primary things that Workflows do is orchestrate the execution of Activities.
An Activity is a normal function or method execution that's intended to execute a single, well-defined action (either short or long-running), such as querying a database, calling a third-party API, or transcoding a media file.
An Activity can interact with world outside the Temporal Platform or use a Temporal Client to interact with a Cluster.
For the Workflow to be able to execute the Activity, we must define the Activity DefinitionWhat is an Activity Definition?
An Activity Definition is the code that defines the constraints of an Activity Task Execution.
Learn more.
An Activity DefinitionWhat is an Activity?
In day-to-day conversation, the term "Activity" denotes an Activity Type, Activity Definition, or Activity Execution.
Learn more is a combination of the Temporal Java SDK Activity Class implementing a specially annotated interface.
An Activity interface is annotated with @ActivityInterface
and an Activity implementation implements this Activity interface.
To handle Activity types that do not have an explicitly registered handler, you can directly implement a dynamic Activity.
@ActivityInterface
public interface GreetingActivities {
String composeGreeting(String greeting, String language);
}
Each method defined in the Activity interface defines a separate Activity method.
You can annotate each method in the Activity interface with the @ActivityMethod
annotation, but this is completely optional.
The following example uses the @ActivityMethod
annotation for the method defined in the previous example.
@ActivityInterface
public interface GreetingActivities {
@ActivityMethod
String composeGreeting(String greeting, String language);
}
An Activity implementation is a Java class that implements an Activity annotated interface.
// Implementation for the GreetingActivities interface example from in the previous section
static class GreetingActivitiesImpl implements GreetingActivities {
@Override
public String composeGreeting(String greeting, String name) {
return greeting + " " + name + "!";
}
}
Use DynamicActivity
to implement any number of Activity types dynamically.
When an Activity implementation that extends DynamicActivity
is registered, it is called for any Activity type invocation that doesn't have an explicitly registered handler.
The dynamic Activity interface is implemented with the execute
method, as shown in the following example.
// Dynamic Activity implementation
public static class DynamicGreetingActivityImpl implements DynamicActivity {
@Override
public Object execute(EncodedValues args) {
String activityType = Activity.getExecutionContext().getInfo().getActivityType();
return activityType
+ ": "
+ args.get(0, String.class)
+ " "
+ args.get(1, String.class)
+ " from: "
+ args.get(2, String.class);
}
}
Use Activity.getExecutionContext()
to get information about the Activity type that should be implemented dynamically.
Activity parameters
There is no explicit limit to the total number of parameters that an Activity DefinitionWhat is an Activity Definition?
An Activity Definition is the code that defines the constraints of an Activity Task Execution.
Learn more may support.
However, there is a limit of the total size of the data ends up encoded into a gRPC message Payload.
A single argument is limited to a maximum size of 2 MB. And the total size of a gRPC message, which includes all the arguments, is limited to a maximum of 4 MB.
Also, keep in mind that all Payload data is recorded in the Workflow Execution Event HistoryWhat is an Event History?
An append log of Events that represents the full state a Workflow Execution.
Learn more and large Event Histories can affect Worker performance.
This is because the entire Event History could be transferred to a Worker Process with a Workflow TaskWhat is a Workflow Task?
A Workflow Task is a Task that contains the context needed to make progress with a Workflow Execution.
Learn more.
Some SDKs require that you pass context objects, others do not. When it comes to your application data—that is, data that is serialized and encoded into a Payload—we recommend that you use a single object as an argument that wraps the application data passed to Activities. This is so that you can change what data is passed to the Activity without breaking a function or method signature.
An Activity interface can have any number of parameters. All inputs should be serializable by the default Jackson JSON Payload Converter.
When implementing Activities, be mindful of the amount of data that you transfer using the Activity invocation parameters or return values as these are recorded in the Workflow Execution Events History. Large Events Histories can adversely impact performance.
You can create a custom object, and pass it to the Activity interface, as shown in the following example.
@ActivityInterface
public interface YourActivities {
String getCustomObject(CustomObj customobj);
void sendCustomObject(CustomObj customobj, String abc);
}
The execute
method in the dynamic Activity interface implementation takes in EncodedValues
that are inputs to the Activity Execution, as shown in the following example.
// Dynamic Activity implementation
public static class DynamicActivityImpl implements DynamicActivity {
@Override
public Object execute(EncodedValues args) {
String activityType = Activity.getExecutionContext().getInfo().getActivityType();
return activityType
+ ": "
+ args.get(0, String.class)
+ " "
+ args.get(1, String.class)
+ " from: "
+ args.get(2, String.class);
}
}
For more details, see Dynamic Activity Reference.
Activity return values
All data returned from an Activity must be serializable.
There is no explicit limit to the amount of data that can be returned by an Activity, but keep in mind that all return values are recorded in a Workflow Execution Event HistoryWhat is an Event History?
An append log of Events that represents the full state a Workflow Execution.
Learn more.
Activity return values must be serializable and deserializable by the provided DataConverter
.
The execute
method for DynamicActivity
can return type Object.
Ensure that your Workflow or Client can handle an Object type return or is able to convert the Object type response.
- Data ConverterWhat is a Data Converter?
A Data Converter is a Temporal SDK component that serializes and encodes data entering and exiting a Temporal Cluster.
Learn more - Java DataConverter reference: https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/common/converter/DataConverter.html
Activity Type
Activities have a Type that are referred to as the Activity name. The following examples demonstrate how to set a custom name for your Activity Type.
The Activity Type defaults to method name, with the first letter of the method name capitalized, and can be customized using namePrefix()
or {ActivityMethod.name()}
to ensure they are distinct.
In the following example, the Activity Type defaults to ComposeGreeting
.
@ActivityInterface
public interface GreetingActivities {
@ActivityMethod
String composeGreeting(String greeting, String language);
}
To overwrite this default naming and assign a custom Activity Type, use the @ActivityMethod
annotation with the name
parameter.
In the following example, the Activity Type is set to "greet".
@ActivityInterface
public interface GreetingActivities {
@ActivityMethod(name = "greet")
String composeGreeting(String greeting, String language);
}
You can also define a prefix for all of your Activity Types using the namePrefix
parameter with the @ActivityInterface
annotation.
The following example shows a namePrefix
parameter applied to the @ActivityInterface
, and two Activity methods, of which one is defined using the @ActivityMethod
annotation.
@ActivityInterface(namePrefix = "A_")
Public interface GreetingActivities {
String sendGreeting(String input);
@ActivityMethod(name = "abc")
String composeGreeting(String greeting, String language);
}
In this example, the Activity type for the first method is set to A_SendGreeting
.
The Activity type for the method annotated with @ActivityMethod
is set to A_abc
.
Activity Execution
Calls to spawn Activity ExecutionsWhat is an Activity Execution?
An Activity Execution is the full chain of Activity Task Executions.
Learn more are written within a Workflow DefinitionWhat is a Workflow Definition?
A Workflow Definition is the code that defines the constraints of a Workflow Execution.
Learn more.
The call to spawn an Activity Execution generates the ScheduleActivityTask Command.
This results in the set of three Activity TaskWhat is an Activity Task?
An Activity Task contains the context needed to make an Activity Task Execution.
Learn more related Events (ActivityTaskScheduled, ActivityTaskStarted, and ActivityTask[Closed])in your Workflow Execution Event History.
A single instance of the Activities implementation is shared across multiple simultaneous Activity invocations. Therefore, the Activity implementation code must be stateless.
The values passed to Activities through invocation parameters or returned through a result value are recorded in the Execution history. The entire Execution history is transferred from the Temporal service to Workflow Workers when a Workflow state needs to recover. A large Execution history can thus adversely impact the performance of your Workflow.
Therefore, be mindful of the amount of data you transfer through Activity invocation parameters or Return Values. Otherwise, no additional limitations exist on Activity implementations.
Activities are remote procedure calls that must be invoked from within a Workflow using ActivityStub
.
Activities are not executable on their own. You cannot start an Activity Execution by itself.
Note that before an Activity Execution is invoked:
- Activity options (either
setStartToCloseTimeout
What is a Start-To-Close Timeout?
A Start-To-Close Timeout is the maximum time allowed for a single Activity Task Execution.
Learn more orScheduleToCloseTimeout
What is a Schedule-To-Close Timeout?
A Schedule-To-Close Timeout is the maximum amount of time allowed for the overall Activity Execution, from when the first Activity Task is scheduled to when the last Activity Task, in the chain of Activity Tasks that make up the Activity Execution, reaches a Closed status.
Learn more are required) must be set for the Activity. For details, see Set Activity Options and Activity Options reference. - The Activity must be registered with a Worker.
See Worker ProgramHow to develop a Worker Program in Java
Use thenewWorker
method on an instance of aWorkerFactory
to create a new Worker in Java.
Learn more - Activity code must be thread-safe.
Activities should only be instantiated using stubs from within a Workflow.
An ActivityStub
returns a client-side stub that implements an Activity interface.
You can invoke Activities using Workflow.newActivityStub
(type-safe) or Workflow.newUntypedActivityStub
(untyped).
Calling a method on the Activity interface schedules the Activity invocation with the Temporal service, and generates an ActivityTaskScheduled
EventWhat is an Event?
Events are created by the Temporal Cluster in response to external occurrences and Commands generated by a Workflow Execution.
Learn more.
Activities can be invoked synchronously or asynchronously.
Invoking Activities Synchronously
In the following example, we use the type-safe Workflow.newActivityStub
within the "FileProcessingWorkflow" Workflow implementation to create a client-side stub of the FileProcessingActivities
class. We also define ActivityOptions
and set setStartToCloseTimeout
option to one hour.
public class FileProcessingWorkflowImpl implements FileProcessingWorkflow {
private final FileProcessingActivities activities;
public FileProcessingWorkflowImpl() {
this.activities = Workflow.newActivityStub(
FileProcessingActivities.class,
ActivityOptions.newBuilder()
.setStartToCloseTimeout(Duration.ofHours(1))
.build());
}
@Override
public void processFile(Arguments args) {
String localName = null;
String processedName = null;
try {
localName = activities.download(args.getSourceBucketName(), args.getSourceFilename());
processedName = activities.processFile(localName);
activities.upload(args.getTargetBucketName(), args.getTargetFilename(), processedName);
} finally {
if (localName != null) {
activities.deleteLocalFile(localName);
}
if (processedName != null) {
activities.deleteLocalFile(processedName);
}
}
}
// ...
}
A Workflow can have multiple Activity stubs. Each Activity stub can have its own ActivityOptions
defined.
The following example shows a Workflow implementation with two typed Activity stubs.
public FileProcessingWorkflowImpl() {
ActivityOptions options1 = ActivityOptions.newBuilder()
.setTaskQueue("taskQueue1")
.setStartToCloseTimeout(Duration.ofMinutes(10))
.build();
this.store1 = Workflow.newActivityStub(FileProcessingActivities.class, options1);
ActivityOptions options2 = ActivityOptions.newBuilder()
.setTaskQueue("taskQueue2")
.setStartToCloseTimeout(Duration.ofMinutes(5))
.build();
this.store2 = Workflow.newActivityStub(FileProcessingActivities.class, options2);
}
To invoke Activities inside Workflows without referencing the interface it implements, use an untyped Activity stub Workflow.newUntypedActivityStub
.
This is useful when the Activity type is not known at compile time, or to invoke Activities implemented in different programming languages.
// Workflow code
ActivityOptions activityOptions =
ActivityOptions.newBuilder()
.setStartToCloseTimeout(Duration.ofSeconds(3))
.setTaskQueue("simple-queue-node")
.build();
ActivityStub activity = Workflow.newUntypedActivityStub(activityOptions);
activity.execute("ComposeGreeting", String.class, "Hello World" , "Spanish");
Invoking Activities Asynchronously
Sometimes Workflows need to perform certain operations in parallel.
The Temporal Java SDK provides the Async
class which includes static methods used to invoke any Activity asynchronously.
The calls return a result of type Promise
which is similar to the Java Future
and CompletionStage
.
When invoking Activities, use Async.function
for Activities that return a result, and Async.procedure
for Activities that return void.
In the following asynchronous Activity invocation, the method reference is passed to Async.function
followed by Activity arguments.
Promise<String> localNamePromise = Async.function(activities::download, sourceBucket, sourceFile);
The following example shows how to call two Activity methods, "download" and "upload", in parallel on multiple files.
public void processFile(Arguments args) {
List<Promise<String>> localNamePromises = new ArrayList<>();
List<String> processedNames = null;
try {
// Download all files in parallel.
for (String sourceFilename : args.getSourceFilenames()) {
Promise<String> localName =
Async.function(activities::download, args.getSourceBucketName(), sourceFilename);
localNamePromises.add(localName);
}
List<String> localNames = new ArrayList<>();
for (Promise<String> localName : localNamePromises) {
localNames.add(localName.get());
}
processedNames = activities.processFiles(localNames);
// Upload all results in parallel.
List<Promise<Void>> uploadedList = new ArrayList<>();
for (String processedName : processedNames) {
Promise<Void> uploaded =
Async.procedure(
activities::upload,
args.getTargetBucketName(),
args.getTargetFilename(),
processedName);
uploadedList.add(uploaded);
}
// Wait for all uploads to complete.
Promise.allOf(uploadedList).get();
} finally {
for (Promise<String> localNamePromise : localNamePromises) {
// Skip files that haven't completed downloading.
if (localNamePromise.isCompleted()) {
activities.deleteLocalFile(localNamePromise.get());
}
}
if (processedNames != null) {
for (String processedName : processedNames) {
activities.deleteLocalFile(processedName);
}
}
}
}
Activity Execution Context
ActivityExecutionContext
is a context object passed to each Activity implementation by default.
You can access it in your Activity implementations via Activity.getExecutionContext()
.
It provides getters to access information about the Workflow that invoked the Activity.
Note that the Activity context information is stored in a thread-local variable.
Therefore, calls to getExecutionContext()
succeed only within the thread that invoked the Activity function.
Following is an example of using the ActivityExecutionContext
:
public class FileProcessingActivitiesImpl implements FileProcessingActivities {
@Override
public String download(String bucketName, String remoteName, String localName) {
ActivityExecutionContext ctx = Activity.getExecutionContext();
ActivityInfo info = ctx.getInfo();
log.info("namespace=" + info.getActivityNamespace());
log.info("workflowId=" + info.getWorkflowId());
log.info("runId=" + info.getRunId());
log.info("activityId=" + info.getActivityId());
log.info("activityTimeout=" + info.getStartToCloseTimeout();
return downloadFileFromS3(bucketName, remoteName, localDirectory + localName);
}
...
}
For details on getting the results of an Activity Execution, see Activity Execution ResultHow to get the result of an Activity Execution
To get the results of an asynchronously invoked Activity method, use the Promise
get
method to block until the Activity method result is available.
Learn more.
Required timeout
Activity Execution semantics rely on several parameters.
The only required value that needs to be set is either a Schedule-To-Close TimeoutWhat is a Start-To-Close Timeout?
A Start-To-Close Timeout is the maximum time allowed for a single Activity Task Execution.
Learn more or a Start-To-Close TimeoutWhat is a Start-To-Close Timeout?
A Start-To-Close Timeout is the maximum time allowed for a single Activity Task Execution.
Learn more.
These values are set in the Activity Options.
Set your Activity Timeout from the ActivityOptions.Builder
class.
Available timeouts are:
- ScheduleToCloseTimeout()
- ScheduleToStartTimeout()
- StartToCloseTimeout()
You can set Activity Options using an ActivityStub
within a Workflow implementation, or per-Activity using WorkflowImplementationOptions
within a Worker.
The following uses ActivityStub
.
GreetingActivities activities = Workflow.newActivityStub(GreetingActivities.class,
ActivityOptions.newBuilder()
.setScheduleToCloseTimeout(Duration.ofSeconds(5))
// .setStartToCloseTimeout(Duration.ofSeconds(2)
// .setScheduletoCloseTimeout(Duration.ofSeconds(20))
.build());
The following uses WorkflowImplementationOptions
.
WorkflowImplementationOptions options =
WorkflowImplementationOptions.newBuilder()
.setActivityOptions(
ImmutableMap.of(
"GetCustomerGreeting",
// Set Activity Execution timeout
ActivityOptions.newBuilder()
.setScheduleToCloseTimeout(Duration.ofSeconds(5))
// .setStartToCloseTimeout(Duration.ofSeconds(2))
// .setScheduleToStartTimeout(Duration.ofSeconds(5))
.build()))
.build();
If you define options per-Activity Type options with WorkflowImplementationOptions.setActivityOptions()
, setting them again specifically with ActivityStub
in a Workflow will override this setting.
Activity Execution Result
The call to spawn an Activity ExecutionWhat is an Activity Execution?
An Activity Execution is the full chain of Activity Task Executions.
Learn more generates the ScheduleActivityTask Command and provides the Workflow with an Awaitable.
Workflow Executions can either block progress until the result is available through the Awaitable or continue progressing, making use of the result when it becomes available.
To get the results of an asynchronously invoked Activity method, use the Promise
get
method to block until the Activity method result is available.
Sometimes an Activity Execution lifecycle goes beyond a synchronous method invocation. For example, a request can be put in a queue and later a reply comes and is picked up by a different Worker process. The whole request-reply interaction can be modeled as a single Activity.
To indicate that an Activity should not be completed upon its method return, call ActivityExecutionContext.doNotCompleteOnReturn()
from the original Activity thread.
Then later, when replies come, complete the Activity using the ActivityCompletionClient
.
To correlate Activity invocation with completion, use either a TaskToken
or Workflow and Activity Ids.
Following is an example of using ActivityExecutionContext.doNotCompleteOnReturn()
:
public class FileProcessingActivitiesImpl implements FileProcessingActivities {
public String download(String bucketName, String remoteName, String localName) {
ActivityExecutionContext ctx = Activity.getExecutionContext();
// Used to correlate reply
byte[] taskToken = ctx.getInfo().getTaskToken();
asyncDownloadFileFromS3(taskToken, bucketName, remoteName, localDirectory + localName);
ctx.doNotCompleteOnReturn();
// Return value is ignored when doNotCompleteOnReturn was called.
return "ignored";
}
...
}
When the download is complete, the download service potentially can complete the Activity, or fail it from a different process, for example:
public <R> void completeActivity(byte[] taskToken, R result) {
completionClient.complete(taskToken, result);
}
public void failActivity(byte[] taskToken, Exception failure) {
completionClient.completeExceptionally(taskToken, failure);
}
Run a dev Worker
The Worker ProcessWhat is a Worker Process?
A Worker Process is responsible for polling a Task Queue, dequeueing a Task, executing your code in response to a Task, and responding to the Temporal Server with the results.
Learn more is where Workflow Functions and Activity Functions are executed.
- Each Worker EntityWhat is a Worker Entity?
A Worker Entity is the individual Worker within a Worker Process that listens to a specific Task Queue.
Learn more in the Worker Process must register the exact Workflow Types and Activity Types it may execute. - Each Worker Entity must also associate itself with exactly one Task QueueWhat is a Task Queue?
A Task Queue is a first-in, first-out queue that a Worker Process polls for Tasks.
Learn more. - Each Worker Entity polling the same Task Queue must be registered with the same Workflow Types and Activity Types.
A Worker EntityWhat is a Worker Entity?
A Worker Entity is the individual Worker within a Worker Process that listens to a specific Task Queue.
Learn more is the component within a Worker Process that listens to a specific Task Queue.
Although multiple Worker Entities can be in a single Worker Process, a single Worker Entity Worker Process may be perfectly sufficient. For more information, see the Worker tuning guide.
A Worker Entity contains both a Workflow Worker and an Activity Worker so that it can make progress for either a Workflow Execution or an Activity Execution.
Use the newWorker
method on an instance of a WorkerFactory
to create a new Worker in Java.
A single Worker Entity can contain many Worker Objects.
Call the start()
method on the instance of the WorkerFactory
to start all the Workers created in this process.
// ...
import io.temporal.client.WorkflowClient;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.worker.Worker;
import io.temporal.worker.WorkerFactory;
public class YourWorker {
public static void main(String[] args) {
WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
WorkflowClient client = WorkflowClient.newInstance(service);
WorkerFactory factory = WorkerFactory.newInstance(client);
Worker yourWorker = factory.newWorker("your_task_queue");
// Register Workflow
// and/or register Activities
factory.start();
}
}
After creating the Worker entity, register all Workflow Types and all Activity Types that the Worker can execute. A Worker can be registered with just Workflows, just Activities, or both.
Operation guides:
Register types
All Workers listening to the same Task Queue name must be registered to handle the exact same Workflows Types and Activity Types.
If a Worker polls a Task for a Workflow Type or Activity Type it does not know about, it fails that Task. However, the failure of the Task does not cause the associated Workflow Execution to fail.
Use worker.registerWorkflowImplementationTypes
to register Workflow Type and worker.registerActivitiesImplementations
to register Activity implementation with Workers.
For Workflows, the Workflow Type is registered with a Worker. A Workflow Type can be registered only once per Worker entity. If you define multiple Workflow implementations of the same type, you get an exception at the time of registration.
For Activities, Activity implementation instances are registered with a Worker because they are stateless and thread-safe. You can pass any number of dependencies in the Activity implementation constructor, such as the database connections, services, etc.
The following example shows how to register a Workflow and an Activity with a Worker.
Worker worker = workerFactory.newWorker("your_task_queue");
...
// Register Workflow
worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class);
// Register Activity
worker.registerActivitiesImplementations(new GreetingActivitiesImpl());
When you register a single instance of an Activity, you can have multiple instances of Workflow Executions calling the same Activity. Activity code must be thread-safe because the same instance of the Activity code is run for every Workflow Execution that calls it.
For DynamicWorkflow
, only one Workflow implementation that extends DynamicWorkflow
can be registered with a Worker.
The following example shows how to register the DynamicWorkflow
and DynamicActivity
implementation with a Worker.
public static void main(String[] arg) {
WorkflowServiceStubs service = WorkflowServiceStubs.newInstance();
WorkflowClient client = WorkflowClient.newInstance(service);
WorkerFactory factory = WorkerFactory.newInstance(client);
Worker worker = factory.newWorker(TASK_QUEUE);
/* Register the Dynamic Workflow implementation with the Worker. Workflow implementations
** must be known to the Worker at runtime to dispatch Workflow Tasks.
*/
worker.registerWorkflowImplementationTypes(DynamicGreetingWorkflowImpl.class);
// Start all the Workers that are in this process.
factory.start();
/* Create the Workflow stub. Note that the Workflow Type is not explicitly registered with the Worker. */
WorkflowOptions workflowOptions =
WorkflowOptions.newBuilder().setTaskQueue(TASK_QUEUE).setWorkflowId(WORKFLOW_ID).build();
WorkflowStub workflow = client.newUntypedWorkflowStub("DynamicWF", workflowOptions);
/**
* Register Dynamic Activity implementation with the Worker. Since Activities are stateless
* and thread-safe, we need to register a shared instance.
*/
worker.registerActivitiesImplementations(new DynamicGreetingActivityImpl());
/* Start Workflow Execution and immmediately send Signal. Pass in the Workflow args and Signal args. */
workflow.signalWithStart("greetingSignal", new Object[] {"John"}, new Object[] {"Hello"});
// Wait for the Workflow to finish getting the results.
String result = workflow.getResult(String.class);
System.out.println(result);
System.exit(0);
}
}
You can register multiple type-specific Workflow implementations alongside a single DynamicWorkflow
implementation.
You can register only one Activity instance that implements DynamicActivity
with a Worker.
Start Workflow Execution
Workflow Execution semantics rely on several parameters—that is, to start a Workflow Execution you must supply a Task Queue that will be used for the Tasks (one that a Worker is polling), the Workflow Type, language-specific contextual data, and Workflow Function parameters.
In the examples below, all Workflow Executions are started using a Temporal Client. To spawn Workflow Executions from within another Workflow Execution, use either the Child Workflow or External Workflow APIs.
See the Customize Workflow Type section to see how to customize the name of the Workflow Type.
A request to spawn a Workflow Execution causes the Temporal Cluster to create the first Event (WorkflowExecutionStarted) in the Workflow Execution Event History. The Temporal Cluster then creates the first Workflow Task, resulting in the first WorkflowTaskScheduled Event.
Use WorkflowStub
to start a Workflow Execution from within a Client, and ExternalWorkflowStub
to start a different Workflow Execution from within a Workflow.
See SignalwithStart
How to send a Signal-with-Start in Java
To send Signals to a Workflow Execution whose status is unknown, use SignalWithStart
with a WorkflowStub
in the Client code.
Learn more to start a Workflow Execution to receive a Signal from within another Workflow.
Using WorkflowStub
WorkflowStub
is a proxy generated by the WorkflowClient
.
Each time a new Workflow Execution is started, an instance of the Workflow implementation object is created.
Then, one of the methods (depending on the Workflow Type of the instance) annotated with @WorkflowMethod
can be invoked.
As soon as this method returns, the Workflow Execution is considered to be complete.
You can use a typed or untyped WorkflowStub
in the client code.
- Typed
WorkflowStub
are useful because they are type safe and allow you to invoke your Workflow methods such as@WorkflowMethod
,@QueryMethod
, and@SignalMethod
directly. - An untyped
WorkflowStub
does not use the Workflow interface, and is not type safe. It is more flexible because it has methods from theWorkflowStub
interface, such asstart
,signalWithStart
,getResults
(sync and async),query
,signal
,cancel
andterminate
. Note that the Temporal Java SDK also provides typedWorkflowStub
versions for these methods. When using untypedWorkflowStub
, we rely on the Workflow Type, Activity Type, Child Workflow Type, as well as Query and Signal names. For details, see Temporal ClientHow to create a Temporal Client in Java
To initialize a Workflow Client, create an instance of aWorkflowClient
, create a client-sideWorkflowStub
, and then call a Workflow method (annotated with the@WorkflowMethod
annotation).
Learn more.
A Workflow Execution can be started either synchronously or asynchronously.
Synchronous invocation starts a Workflow and then waits for its completion. If the process that started the Workflow crashes or stops waiting, the Workflow continues executing. Because Workflows are potentially long-running, and Client crashes happen, it is not very commonly found in production use. The following example is a type-safe approach for starting a Workflow Execution synchronously.
NotifyUserAccounts workflow = client.newWorkflowStub(
NotifyUserAccounts.class,
WorkflowOptions.newBuilder()
.setWorkflowId("notifyAccounts")
.setTaskQueue(taskQueue)
.build()
);
// start the Workflow and wait for a result.
workflow.notify(new String[] { "Account1", "Account2", "Account3", "Account4", "Account5",
"Account6", "Account7", "Account8", "Account9", "Account10"});
}
// notify(String[] accountIds) is a Workflow method defined in the Workflow Definition.Asynchronous start initiates a Workflow Execution and immediately returns to the caller. This is the most common way to start Workflows in production code. The
WorkflowClient
https://github.com/temporalio/sdk-java/blob/master/temporal-sdk/src/main/java/io/temporal/client/WorkflowClient.java) provides some static methods, such asstart
,execute
,signalWithStart
etc., that help with starting your Workflows asynchronously.The following examples show how to start Workflow Executions asynchronously, with either typed or untyped
WorkflowStub
.Typed WorkflowStub Example
// create typed Workflow stub
FileProcessingWorkflow workflow = client.newWorkflowStub(FileProcessingWorkflow.class,
WorkflowOptions.newBuilder()
.setTaskQueue(taskQueue)
.setWorkflowId(workflowId)
.build());
// use WorkflowClient.execute to return future that contains Workflow result or failure, or
// use WorkflowClient.start to return WorkflowId and RunId of the started Workflow).
WorkflowClient.start(workflow::greetCustomer);Untyped WorkflowStub Example
WorkflowStub untyped = client.newUntypedWorkflowStub("FileProcessingWorkflow",
WorkflowOptions.newBuilder()
.setWorkflowId(workflowId)
.setTaskQueue(taskQueue)
.build());
// blocks until Workflow Execution has been started (not until it completes)
untyped.start(argument);
You can call a Dynamic Workflow implementation using an untyped WorkflowStub
.
The following example shows how to call the Dynamic Workflow implementation in the Client code.
WorkflowClient client = WorkflowClient.newInstance(service);
/**
* Note that for this part of the client code, the dynamic Workflow implementation must
* be known to the Worker at runtime in order to dispatch Workflow tasks, and may be defined
* in the Worker definition as:*/
// worker.registerWorkflowImplementationTypes(DynamicGreetingWorkflowImpl.class);
/* Create the Workflow stub to call the dynamic Workflow.
* Note that the Workflow Type is not explicitly registered with the Worker.*/
WorkflowOptions workflowOptions =
WorkflowOptions.newBuilder().setTaskQueue(TASK_QUEUE).setWorkflowId(WORKFLOW_ID).build();
WorkflowStub workflow = client.newUntypedWorkflowStub("DynamicWF", workflowOptions);
DynamicWorkflow
can be used to invoke different Workflow Types.
To check what type is running when your Dynamic Workflow execute
method runs, use getWorkflowType()
in the implementation code.
String type = Workflow.getInfo().getWorkflowType();
See Workflow Execution ResultHow to get the result of a Workflow Execution in Java
A synchronous Workflow Execution blocks your client thread until the Workflow Execution completes (or fails) and get the results (or error in case of failure). An asynchronous Workflow Execution immediately returns a value to the caller.
Learn more for details on how to get the results of the Workflow Execution.
Using ExternalWorkflowStub
Use ExternalWorkflowStub
within a Workflow to invoke, and send Signals to, other Workflows by type.
This helps particularly for executing Workflows written in other language SDKs, as shown in the following example.
@Override
public String yourWFMethod(String name) {
ExternalWorkflowStub callOtherWorkflow = Workflow.newUntypedExternalWorkflowStub("OtherWFId");
}
See the Temporal Polyglot code for examples of executing Workflows written in other language SDKs.
Recurring start
You can start a Workflow Execution on a regular schedule by using setCronSchedule
How to set a Cron Schedule in Java
Set the Cron Schedule with the WorkflowStub
instance in the Client code using [WorkflowOptions.Builder.setCronSchedule
Learn more Workflow option in the Client code.
Set Task Queue
In most SDKs, the only Workflow Option that must be set is the name of the Task QueueWhat is a Task Queue?
A Task Queue is a first-in, first-out queue that a Worker Process polls for Tasks.
Learn more.
For any code to execute, a Worker Process must be running that contains a Worker Entity that is polling the same Task Queue name.
Set the Workflow Task Queue with the WorkflowStub
instance in the Client code using WorkflowOptions.Builder.setTaskQueue
.
- Type:
String
- Default: none
//create Workflow stub for YourWorkflowInterface
YourWorkflowInterface workflow1 =
WorkerGreet.greetclient.newWorkflowStub(
GreetWorkflowInterface.class,
WorkflowOptions.newBuilder()
.setWorkflowId("YourWF")
// Set the Task Queue
.setTaskQueue(WorkerGreet.TASK_QUEUE)
.build());
Workflow Id
Although it is not required, we recommend providing your own Workflow IdWhat is a Workflow Id?
A Workflow Id is a customizable, application-level identifier for a Workflow Execution that is unique to an Open Workflow Execution within a Namespace.
Learn more that maps to a business process or business entity identifier, such as an order identifier or customer identifier.
Set the Workflow Id with the WorkflowStub
instance in the Client code using WorkflowOptions.Builder.setWorkflowId
.
- Type:
String
- Default: none
//create Workflow stub for YourWorkflowInterface
YourWorkflowInterface workflow1 =
WorkerGreet.greetclient.newWorkflowStub(
GreetWorkflowInterface.class,
WorkflowOptions.newBuilder()
// Set the Workflow Id
.setWorkflowId("YourWF")
.setTaskQueue(WorkerGreet.TASK_QUEUE)
.build());
Get Workflow results
If the call to start a Workflow Execution is successful, you will gain access to the Workflow Execution's Run Id.
The Workflow Id, Run Id, and Namespace may be used to uniquely identify a Workflow Execution in the system and get its result.
It's possible to both block progress on the result (synchronous execution) or get the result at some other point in time (asynchronous execution).
In the Temporal Platform, it's also acceptable to use Queries as the preferred method for accessing the state and results of Workflow Executions.
A synchronous Workflow Execution blocks your client thread until the Workflow Execution completes (or fails) and get the results (or error in case of failure).
The following example is a type-safe approach for getting the results of a synchronous Workflow Execution.
FileProcessingWorkflow workflow = client.newWorkflowStub(
FileProcessingWorkflow.class,
WorkflowOptions.newBuilder()
.setWorkflowId(workflowId)
.setTaskQueue(taskQueue)
.build();
// start sync and wait for results (or failure)
String result = workflow.processfile(new Argument());
An asynchronous Workflow Execution immediately returns a value to the caller.
The following examples show how to get the results of a Workflow Execution through typed and untyped WorkflowStub
.
Typed WorkflowStub Example
// create typed Workflow stub
FileProcessingWorkflow workflow = client.newWorkflowStub(FileProcessingWorkflow.class,
WorkflowOptions.newBuilder()
.setTaskQueue(taskQueue)
.setWorkflowId(workflowId)
.build());
// use WorkflowClient.execute (if your Workflow takes in arguments) or WorkflowClient.start (for zero arguments)
WorkflowClient.start(workflow::greetCustomer);Untyped WorkflowStub Example
WorkflowStub untyped = client.newUntypedWorkflowStub("FileProcessingWorkflow",
WorkflowOptions.newBuilder()
.setWorkflowId(workflowId)
.setTaskQueue(taskQueue)
.build());
// blocks until Workflow Execution has been started (not until it completes)
untyped.start(argument);
If you need to wait for a Workflow Execution to complete after an asynchronous start, the most straightforward way is to call the blocking Workflow instance again.
Note that if WorkflowOptions.WorkflowIdReusePolicy
is not set to AllowDuplicate
, then instead of throwing DuplicateWorkflowException
, it reconnects to an existing Workflow and waits for its completion.
The following example shows how to do this from a different process than the one that started the Workflow Execution.
YourWorkflow workflow = client.newWorkflowStub(YourWorkflow.class, workflowId);
// Returns the result after waiting for the Workflow to complete.
String result = workflow.yourMethod();
Another way to connect to an existing Workflow and wait for its completion from another process, is to use UntypedWorkflowStub
. For example:
WorkflowStub workflowStub = client.newUntypedWorkflowStub(workflowType, workflowOptions);
// Returns the result after waiting for the Workflow to complete.
String result = untyped.getResult(String.class);
Get last (successful) completion result
For a Temporal Cron Job, get the result of previous successful runs using GetLastCompletionResult()
.
The method returns null
if there is no previous completion.
The following example shows how to implement this in a Workflow.
public String cronWorkflow() {
String lastProcessedFileName = Workflow.getLastCompletionResult(String.class);
// Process work starting from the lastProcessedFileName.
// Business logic implementation goes here.
// Updates lastProcessedFileName to the new value.
return lastProcessedFileName;
}
Note that this works even if one of the Cron schedule runs failed. The next schedule will still get the last successful result if it ever successfully completed at least once. For example, for a daily cron Workflow, if the run succeeds on the first day and fails on the second day, then the third day run will get the result from first day's run using these APIs.