From 6eeb8e10fbbc8952371fad1183d474dd701cbd11 Mon Sep 17 00:00:00 2001 From: Tihomir Surdilovic Date: Thu, 24 Jul 2025 15:13:01 -0400 Subject: [PATCH 1/5] HelloSignalWithStartAndWorkflowInit sample Signed-off-by: Tihomir Surdilovic --- README.md | 2 +- .../HelloSignalWithStartAndWorkflowInit.java | 209 ++++++++++++++++++ .../java/io/temporal/samples/hello/README.md | 1 + 3 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java diff --git a/README.md b/README.md index 1cbf700c9..bbe7efa71 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ See the README.md file in each main sample directory for cut/paste Gradle comman - [**HelloSignalWithTimer**](/core/src/main/java/io/temporal/samples/hello/HelloSignalWithTimer.java): Demonstrates how to use collect signals for certain amount of time and then process last one. - [**HelloWorkflowTimer**](/core/src/main/java/io/temporal/samples/hello/HelloWorkflowTimer.java): Demonstrates how we can use workflow timer to restrict duration of workflow execution instead of workflow run/execution timeouts. - [**Auto-Heartbeating**](/core/src/main/java/io/temporal/samples/autoheartbeat/): Demonstrates use of Auto-heartbeating utility via activity interceptor. - + - [**HelloSignalWithStartAndWorkflowInit**](/core/src/main/java/io/temporal/samples/hello): Demonstrates how WorkflowInit can be useful with SignalWithStart to initialize workflow variables. #### Scenario-based samples diff --git a/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java b/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java new file mode 100644 index 000000000..3e4465ae7 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java @@ -0,0 +1,209 @@ +package io.temporal.samples.hello; + +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityOptions; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowFailedException; +import io.temporal.client.WorkflowOptions; +import io.temporal.client.WorkflowStub; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import io.temporal.worker.WorkflowImplementationOptions; +import io.temporal.workflow.*; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.lang.StringUtils; + +/** + * Sample Temporal workflow that demonstrates how to use WorkflowInit with clients starting + * execution using SignalWithStart + */ +public class HelloSignalWithStartAndWorkflowInit { + static final String TASK_QUEUE = "HelloWithInitTaskQueue"; + static final String WORKFLOW_ID = "HelloWithInitWorkflowId"; + + public interface MyWorkflow { + @WorkflowMethod + String greet(Person person); + + @SignalMethod + void addGreeting(Person person); + } + + @WorkflowInterface + public interface MyWorkflowWithInit extends MyWorkflow {} + + @WorkflowInterface + public interface MyWorkflowNoInit extends MyWorkflow {} + + public static class WithInitMyWorkflowImpl implements MyWorkflowWithInit { + // We dont initialize peopleToGreet on purpose + private List peopleToGreet; + private MyGreetingActivities activities = + Workflow.newActivityStub( + MyGreetingActivities.class, + ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(2)).build()); + + @WorkflowInit + public WithInitMyWorkflowImpl(Person person) { + peopleToGreet = new ArrayList<>(); + } + + @Override + public String greet(Person person) { + peopleToGreet.add(person); + List greetings = new ArrayList<>(); + + while (!peopleToGreet.isEmpty()) { + // run activity... + greetings.add(activities.greet(peopleToGreet.get(0))); + peopleToGreet.remove(0); + } + return StringUtils.join(greetings, ","); + } + + @Override + public void addGreeting(Person person) { + peopleToGreet.add(person); + } + } + + public static class WithoutInitMyWorkflowImpl implements MyWorkflowNoInit { + // We dont initialize peopleToGreet on purpose + private List peopleToGreet; + private MyGreetingActivities activities = + Workflow.newActivityStub( + MyGreetingActivities.class, + ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(2)).build()); + + @Override + public String greet(Person person) { + peopleToGreet.add(person); + List greetings = new ArrayList<>(); + + while (!peopleToGreet.isEmpty()) { + // run activity... + greetings.add(activities.greet(peopleToGreet.get(0))); + peopleToGreet.remove(0); + } + return StringUtils.join(greetings, ","); + } + + @Override + public void addGreeting(Person person) { + peopleToGreet.add(person); + } + } + + @ActivityInterface + public interface MyGreetingActivities { + public String greet(Person person); + } + + public static class MyGreetingActivitiesImpl implements MyGreetingActivities { + @Override + public String greet(Person person) { + return "Hello " + person.firstName + " " + person.lastName; + } + } + + public static class Person { + String firstName; + String lastName; + int age; + + public Person() {} + + public Person(String firstName, String lastName, int age) { + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + } + + public static void main(String[] args) { + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + WorkflowClient client = WorkflowClient.newInstance(service); + WorkerFactory factory = WorkerFactory.newInstance(client); + Worker worker = factory.newWorker(TASK_QUEUE); + + worker.registerWorkflowImplementationTypes(WithInitMyWorkflowImpl.class); + // We explicitly want to fail this workflow on NPE as thats what we expect without WorkflowInit + // As we didnt initialize peopleToGreet on purpose + worker.registerWorkflowImplementationTypes( + WorkflowImplementationOptions.newBuilder() + .setFailWorkflowExceptionTypes(NullPointerException.class) + .build(), + WithoutInitMyWorkflowImpl.class); + worker.registerActivitiesImplementations(new MyGreetingActivitiesImpl()); + + factory.start(); + + MyWorkflowWithInit withInitStub = + client.newWorkflowStub( + MyWorkflowWithInit.class, + WorkflowOptions.newBuilder() + .setWorkflowId("with-init") + .setTaskQueue(TASK_QUEUE) + .build()); + // Start with init workflow which is expected to succeed + // As WorkflowInit will initialize peopleToGreet before signal handler is invoked + WorkflowStub.fromTyped(withInitStub) + .signalWithStart( + "addGreeting", + new Object[] {new Person("Michael", "Jordan", 55)}, + new Object[] {new Person("John", "Stockton", 57)}); + + String result = WorkflowStub.fromTyped(withInitStub).getResult(String.class); + System.out.println("Result: " + result); + + // Start without init, this execution is expected to fail as we set + // NullPointerException as a workflow failure type + // NPE is caused because we did not initialize peopleToGreet array + MyWorkflowNoInit noInitStub = + client.newWorkflowStub( + MyWorkflowNoInit.class, + WorkflowOptions.newBuilder() + .setWorkflowId("with-init") + .setTaskQueue(TASK_QUEUE) + .build()); + WorkflowStub.fromTyped(noInitStub) + .signalWithStart( + "addGreeting", + new Object[] {new Person("Michael", "Jordan", 55)}, + new Object[] {new Person("John", "Stockton", 57)}); + try { + WorkflowStub.fromTyped(noInitStub).getResult(String.class); + } catch (WorkflowFailedException e) { + System.out.println("Expected workflow failure: " + e.getMessage()); + } + + System.exit(0); + } +} diff --git a/core/src/main/java/io/temporal/samples/hello/README.md b/core/src/main/java/io/temporal/samples/hello/README.md index 41dd18271..c8ebbdb42 100644 --- a/core/src/main/java/io/temporal/samples/hello/README.md +++ b/core/src/main/java/io/temporal/samples/hello/README.md @@ -34,4 +34,5 @@ To run each hello world sample, use one of the following commands: ./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloSideEffect ./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloUpdate ./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloSignalWithTimer +./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloSignalWithStartAndWorkflowInit ``` From a1417e33725ee33cc24c7bf193467b54c77a556d Mon Sep 17 00:00:00 2001 From: Tihomir Surdilovic Date: Thu, 24 Jul 2025 16:02:54 -0400 Subject: [PATCH 2/5] update readme Signed-off-by: Tihomir Surdilovic --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bbe7efa71..a62ed4c98 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ See the README.md file in each main sample directory for cut/paste Gradle comman - [**HelloSignalWithTimer**](/core/src/main/java/io/temporal/samples/hello/HelloSignalWithTimer.java): Demonstrates how to use collect signals for certain amount of time and then process last one. - [**HelloWorkflowTimer**](/core/src/main/java/io/temporal/samples/hello/HelloWorkflowTimer.java): Demonstrates how we can use workflow timer to restrict duration of workflow execution instead of workflow run/execution timeouts. - [**Auto-Heartbeating**](/core/src/main/java/io/temporal/samples/autoheartbeat/): Demonstrates use of Auto-heartbeating utility via activity interceptor. - - [**HelloSignalWithStartAndWorkflowInit**](/core/src/main/java/io/temporal/samples/hello): Demonstrates how WorkflowInit can be useful with SignalWithStart to initialize workflow variables. + - [**HelloSignalWithStartAndWorkflowInit**](/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java): Demonstrates how WorkflowInit can be useful with SignalWithStart to initialize workflow variables. #### Scenario-based samples From cd65f9f31ee6e6f93fe9fb9567cb435c54d42c3a Mon Sep 17 00:00:00 2001 From: Tihomir Surdilovic Date: Thu, 24 Jul 2025 21:23:08 -0400 Subject: [PATCH 3/5] update workflowid Signed-off-by: Tihomir Surdilovic --- .../samples/hello/HelloSignalWithStartAndWorkflowInit.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java b/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java index 3e4465ae7..49cb1cb15 100644 --- a/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java +++ b/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java @@ -190,7 +190,7 @@ public static void main(String[] args) { client.newWorkflowStub( MyWorkflowNoInit.class, WorkflowOptions.newBuilder() - .setWorkflowId("with-init") + .setWorkflowId("without-init") .setTaskQueue(TASK_QUEUE) .build()); WorkflowStub.fromTyped(noInitStub) From 5466b89f086f123b53247ff669c30f751b12e69d Mon Sep 17 00:00:00 2001 From: Tihomir Surdilovic Date: Thu, 24 Jul 2025 21:45:48 -0400 Subject: [PATCH 4/5] adding test Signed-off-by: Tihomir Surdilovic --- .../HelloSignalWithStartAndWorkflowInit.java | 1 - ...lloSignalWithStartAndWorkflowInitTest.java | 76 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInitTest.java diff --git a/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java b/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java index 49cb1cb15..096516b10 100644 --- a/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java +++ b/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java @@ -22,7 +22,6 @@ */ public class HelloSignalWithStartAndWorkflowInit { static final String TASK_QUEUE = "HelloWithInitTaskQueue"; - static final String WORKFLOW_ID = "HelloWithInitWorkflowId"; public interface MyWorkflow { @WorkflowMethod diff --git a/core/src/test/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInitTest.java b/core/src/test/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInitTest.java new file mode 100644 index 000000000..ca5508f72 --- /dev/null +++ b/core/src/test/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInitTest.java @@ -0,0 +1,76 @@ +package io.temporal.samples.hello; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import io.temporal.client.WorkflowFailedException; +import io.temporal.client.WorkflowOptions; +import io.temporal.client.WorkflowStub; +import io.temporal.testing.TestWorkflowEnvironment; +import io.temporal.testing.TestWorkflowExtension; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkflowImplementationOptions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class HelloSignalWithStartAndWorkflowInitTest { + @RegisterExtension + public static final TestWorkflowExtension testWorkflowExtension = + TestWorkflowExtension.newBuilder() + .registerWorkflowImplementationTypes( + HelloSignalWithStartAndWorkflowInit.WithInitMyWorkflowImpl.class) + .registerWorkflowImplementationTypes( + WorkflowImplementationOptions.newBuilder() + .setFailWorkflowExceptionTypes(NullPointerException.class) + .build(), + HelloSignalWithStartAndWorkflowInit.WithoutInitMyWorkflowImpl.class) + .setActivityImplementations( + new HelloSignalWithStartAndWorkflowInit.MyGreetingActivitiesImpl()) + .build(); + + @Test + public void testWithInit(TestWorkflowEnvironment testEnv, Worker worker) { + HelloSignalWithStartAndWorkflowInit.MyWorkflowWithInit withInitStub = + testEnv + .getWorkflowClient() + .newWorkflowStub( + HelloSignalWithStartAndWorkflowInit.MyWorkflowWithInit.class, + WorkflowOptions.newBuilder() + .setWorkflowId("with-init") + .setTaskQueue(worker.getTaskQueue()) + .build()); + WorkflowStub.fromTyped(withInitStub) + .signalWithStart( + "addGreeting", + new Object[] {new HelloSignalWithStartAndWorkflowInit.Person("Michael", "Jordan", 55)}, + new Object[] {new HelloSignalWithStartAndWorkflowInit.Person("John", "Stockton", 57)}); + String result = WorkflowStub.fromTyped(withInitStub).getResult(String.class); + assertEquals("Hello Michael Jordan,Hello John Stockton", result); + } + + @Test + public void testWithoutInit(TestWorkflowEnvironment testEnv, Worker worker) { + HelloSignalWithStartAndWorkflowInit.MyWorkflowNoInit noInitStub = + testEnv + .getWorkflowClient() + .newWorkflowStub( + HelloSignalWithStartAndWorkflowInit.MyWorkflowNoInit.class, + WorkflowOptions.newBuilder() + .setWorkflowId("without-init") + .setTaskQueue(worker.getTaskQueue()) + .build()); + WorkflowStub.fromTyped(noInitStub) + .signalWithStart( + "addGreeting", + new Object[] {new HelloSignalWithStartAndWorkflowInit.Person("Michael", "Jordan", 55)}, + new Object[] {new HelloSignalWithStartAndWorkflowInit.Person("John", "Stockton", 57)}); + try { + WorkflowStub.fromTyped(noInitStub).getResult(String.class); + fail("Workflow execution should have failed"); + } catch (Exception e) { + if (!(e instanceof WorkflowFailedException)) { + fail("Workflow execution should have failed"); + } + } + } +} From caf216942cae8c82386a2800807ad91b10051c09 Mon Sep 17 00:00:00 2001 From: Tihomir Surdilovic Date: Mon, 28 Jul 2025 10:05:08 -0400 Subject: [PATCH 5/5] Update core/src/test/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInitTest.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Antonio Mendoza Pérez --- .../samples/hello/HelloSignalWithStartAndWorkflowInitTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInitTest.java b/core/src/test/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInitTest.java index ca5508f72..210967b28 100644 --- a/core/src/test/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInitTest.java +++ b/core/src/test/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInitTest.java @@ -69,7 +69,7 @@ public void testWithoutInit(TestWorkflowEnvironment testEnv, Worker worker) { fail("Workflow execution should have failed"); } catch (Exception e) { if (!(e instanceof WorkflowFailedException)) { - fail("Workflow execution should have failed"); + fail("Workflow execution should have failed with WorkflowFailedException"); } } }