Skip to main content

How to use Signals in Java

To use Signals in Java, initiate the Signal method with @SignalMethod annotation in the Workflow interface and call the Signal method either directly from the Client or use ExternalWorkflowStub to call the Signal method from within another Workflow.

A Signal method can be called from either a Client or another Workflow to send Signals to this Workflow.

Note that you can send a Signal only to running Workflow Executions. You can use Signals to update the state of a running Workflow Execution.

Define Signal Method

The @SignalMethod annotation indicates that the method is used to handle and react to external Signals.

 @SignalMethod
void mySignal(String signalName);

The method can have parameters that contain the Signal payload and must be serializable by the default Jackson JSON Payload Converter.

void mySignal(String signalName, Object... args);

This method does not return a value and must have a void return type.

Things to consider when defining Signals:

  • Use Workflow object constructors and initialization blocks to initialize the internal data structures if possible.
  • Signals might be received by a Workflow before the Workflow method is executed. When implementing Signals in scenarios where this can occur, assume that no parts of Workflow code ran. In some cases, Signal method implementation might require some initialization to be performed by the Workflow method code first—for example, when the Signal processing depends on, and is defined by the Workflow input. In this case, you can use a flag to determine whether the Workflow method is already triggered; if not, persist the Signal data into a collection for delayed processing by the Workflow method.

Handle Signal

Use the @SignalMethod annotation to handle Signals in the Workflow interface.

The Signal type defaults to the name of the method. In the following example, the Signal type defaults to retryNow.

@WorkflowInterface
public interface FileProcessingWorkflow {

@WorkflowMethod
String processFile(Arguments args);

@SignalMethod
void retryNow();
}

To overwrite this default naming and assign a custom Signal type, use the @SignalMethod annotation with the name parameter. In the following example, the Signal type is set to retrysignal.

@WorkflowInterface
public interface FileProcessingWorkflow {

@WorkflowMethod
String processFile(Arguments args);

@SignalMethod(name = "retrysignal")
void retryNow();
}

A Workflow interface can define any number of methods annotated with @SignalMethod, but the method names or the name parameters for each must be unique.

In the following example, we define a Signal method updateGreeting to update the greeting in the Workflow. We set a Workflow.await in the Workflow implementation to block the current Workflow Execution until the provided unblock condition is evaluated to true. In this case, the unblocking condition is evaluated to true when the Signal to update the greeting is received.

@WorkflowInterface
public interface HelloWorld {
@WorkflowMethod
void sayHello(String name);

@SignalMethod
void updateGreeting(String greeting);
}
public class HelloWorldImpl implements HelloWorld {
private final Logger workflowLogger = Workflow.getLogger(HelloWorldImpl.class);
private String greeting;

@Override
public void sayHello(String name) {
int count = 0;
while (!"Bye".equals(greeting)) {
String oldGreeting = greeting;
Workflow.await(() -> !Objects.equals(greeting, oldGreeting));
}
workflowLogger.info(++count + ": " + greeting + " " + name + "!");
}

@Override
public void updateGreeting(String greeting) {
this.greeting = greeting;
}
}

This Workflow completes when the Signal updates the greeting to Bye.

Dynamic Signal Handler You can also implement Signal handlers dynamically. This is useful for library-level code and implementation of DSLs.

Use Workflow.registerListener(Object) to register an implementation of the DynamicSignalListener in the Workflow implementation code.

      Workflow.registerListener(
(DynamicSignalHandler)
(signalName, encodedArgs) -> name = encodedArgs.get(0, String.class));

When registered, any Signals sent to the Workflow without a defined handler will be delivered to the DynamicSignalHandler. Note that you can only register one Workflow.registerListener(Object) per Workflow Execution. DynamicSignalHandler can be implemented in both regular and dynamic Workflow implementations.

Send Signal from Temporal Client

To send a Signal to a Workflow Execution from a Client, call the Signal method, annotated with @SignalMethod in the Workflow interface, from the Client code.

In the following Client code example, we start the Workflow greetCustomer and call the Signal method addCustomer that is handled in the Workflow.

// create a typed Workflow stub for GreetingsWorkflow
GreetingsWorkflow workflow = client.newWorkflowStub(GreetingsWorkflow.class,
WorkflowOptions.newBuilder()
// set the Task Queue
.setTaskQueue(taskQueue)
// Workflow Id is recommended but not required
.setWorkflowId(workflowId)
.build());

// start the Workflow
WorkflowClient.start(workflow::greetCustomer);
// send a Signal to the Workflow
Customer customer = new Customer("John", "Spanish", "john@john.com");
workflow.addCustomer(customer); //addCustomer is the Signal method defined in the greetCustomer Workflow.

See Handle Signals for details on how to handle Signals in a Workflow.

Send Signal from within a Workflow

To send a Signal from within a Workflow to a different Workflow Execution, initiate an ExternalWorkflowStub in the implementation of the current Workflow and call the Signal method defined in the other Workflow.

The following example shows how to use an untyped ExternalWorkflowStub in the Workflow implementation to send a Signal to another Workflow.

    public String sendGreeting(String name) {

// initiate ExternalWorkflowStub to call another Workflow by its Id "ReplyWF"
ExternalWorkflowStub callRespondWorkflow = Workflow.newUntypedExternalWorkflowStub("ReplyWF");

String responseTrigger = activity.greeting("Hello", name);

// send a Signal from this sendGreeting Workflow to the other Workflow
// by calling the Signal method name "getGreetCall" defined in that Workflow.
callRespondWorkflow.signal("getGreetCall", responseTrigger);

return responseTrigger;

Signal-With-Start

To send Signals to a Workflow Execution whose status is unknown, use SignalWithStart with a WorkflowStub in the Client code. This method ensures that if the Workflow Execution is in a closed state, a new Workflow Execution is spawned and the Signal is delivered to the running Workflow Execution.

Note that when the SignalwithStart spawns a new Workflow Execution, the Signal is delivered before the call to your @WorkflowMethod. This means that the Signal handler in your Workflow interface code will execute before the @WorkfowMethod. You must ensure that your code logic can deal with this.

In the following example, the Client code uses SignalwithStart to send the Signal setCustomer to the UntypedWorkflowStub named GreetingWorkflow. If the GreetingWorkflow Workflow Execution is not running, the SignalwithStart starts the Workflow Execution.

...
public static void signalWithStart() {
// WorkflowStub is a client-side stub to a single Workflow instance
WorkflowStub untypedWorkflowStub = client.newUntypedWorkflowStub("GreetingWorkflow",
WorkflowOptions.newBuilder()
.setWorkflowId(workflowId)
.setTaskQueue(taskQueue)
.build());

untypedWorkflowStub.signalWithStart("setCustomer", new Object[] {customer2}, new Object[] {customer1});

printWorkflowStatus();

try {
String greeting = untypedWorkflowStub.getResult(String.class);
printWorkflowStatus();
System.out.println("Greeting: " + greeting);
} catch(WorkflowFailedException e) {
System.out.println("Workflow failed: " + e.getCause().getMessage());
printWorkflowStatus();
}
}
...

The following example shows the Workflow interface for the GreetingWorkflow called in the previous example.

...
@WorkflowInterface
public interface GreetingWorkflow {
@WorkflowMethod
String greet(Customer customer);

@SignalMethod
void setCustomer(Customer customer);

@QueryMethod
Customer getCustomer();
...
}

Note that the Signal handler setCustomer is executed before the @WorkflowMethod greet is called.