Skip to content

Commit f2c7038

Browse files
Samples for Standalone Activities (#778)
* Java hello/full samples * Capitalize type name * javaSDKVersion = '1.35.0'
1 parent 0a4092d commit f2c7038

15 files changed

Lines changed: 668 additions & 1 deletion

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ See the README.md file in each main sample directory for cut/paste Gradle comman
8181
- [**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.
8282
- [**Auto-Heartbeating**](/core/src/main/java/io/temporal/samples/autoheartbeat/): Demonstrates use of Auto-heartbeating utility via activity interceptor.
8383
- [**HelloSignalWithStartAndWorkflowInit**](/core/src/main/java/io/temporal/samples/hello/HelloSignalWithStartAndWorkflowInit.java): Demonstrates how WorkflowInit can be useful with SignalWithStart to initialize workflow variables.
84+
- [**HelloStandaloneActivity**](/core/src/main/java/io/temporal/samples/hello/HelloStandaloneActivity.java): Demonstrates how to execute a Standalone Activity directly from an ActivityClient, without a Workflow.
8485

8586
#### Scenario-based samples
8687

@@ -147,6 +148,8 @@ Load client configuration from TOML files with programmatic overrides.
147148

148149
- [**Exclude Workflow/ActivityTypes from Interceptors**](/core/src/main/java/io/temporal/samples/excludefrominterceptor): Demonstrates how to exclude certain workflow / activity types from interceptors.
149150

151+
- [**Standalone Activities**](/core/src/main/java/io/temporal/samples/standaloneactivities): Demonstrates how to start, execute, list, and count Standalone Activities — Activities that run independently without a Workflow, using ActivityClient.
152+
150153
#### SDK Metrics
151154

152155
- [**Set up SDK metrics**](/core/src/main/java/io/temporal/samples/metrics): Demonstrates how to set up and scrape SDK metrics.

build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ subprojects {
2626
ext {
2727
otelVersion = '1.30.1'
2828
otelVersionAlpha = "${otelVersion}-alpha"
29-
javaSDKVersion = '1.34.0'
29+
javaSDKVersion = '1.35.0'
3030
camelVersion = '3.22.1'
3131
jarVersion = '1.0.0'
3232
}
3333

3434
repositories {
35+
mavenLocal()
3536
maven {
3637
url "https://oss.sonatype.org/content/repositories/snapshots/"
3738
}

core/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,7 @@ dependencies {
6161
task execute(type: JavaExec) {
6262
mainClass = findProperty("mainClass") ?: ""
6363
classpath = sourceSets.main.runtimeClasspath
64+
if (findProperty("args")) {
65+
args findProperty("args").tokenize()
66+
}
6467
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package io.temporal.samples.hello;
2+
3+
import io.temporal.activity.ActivityInterface;
4+
import io.temporal.activity.ActivityMethod;
5+
import io.temporal.client.ActivityClient;
6+
import io.temporal.client.ActivityClientOptions;
7+
import io.temporal.client.StartActivityOptions;
8+
import io.temporal.client.WorkflowClient;
9+
import io.temporal.envconfig.ClientConfigProfile;
10+
import io.temporal.serviceclient.WorkflowServiceStubs;
11+
import io.temporal.worker.Worker;
12+
import io.temporal.worker.WorkerFactory;
13+
import java.io.IOException;
14+
import java.time.Duration;
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
17+
18+
/**
19+
* Sample Temporal application that executes a Standalone Activity — an Activity that runs
20+
* independently, without being orchestrated by a Workflow. Requires a local instance of the
21+
* Temporal service to be running.
22+
*
23+
* <p>Unlike regular Activities, a Standalone Activity is started directly from a Temporal Client
24+
* using {@link ActivityClient}, not from inside a Workflow Definition. Writing the Activity and
25+
* registering it with the Worker is identical in both cases.
26+
*/
27+
public class HelloStandaloneActivity {
28+
29+
static final String TASK_QUEUE = "HelloStandaloneActivityTaskQueue";
30+
static final String ACTIVITY_ID = "hello-standalone-activity-id";
31+
32+
/**
33+
* Activity interface. Writing a Standalone Activity is identical to writing an Activity
34+
* orchestrated by a Workflow — the same Activity can be used for both.
35+
*
36+
* @see io.temporal.activity.ActivityInterface
37+
* @see io.temporal.activity.ActivityMethod
38+
*/
39+
@ActivityInterface
40+
public interface GreetingActivities {
41+
42+
// Define your activity method which can be called directly from a Temporal Client.
43+
@ActivityMethod
44+
String composeGreeting(String greeting, String name);
45+
}
46+
47+
/** Simple activity implementation that concatenates two strings. */
48+
public static class GreetingActivitiesImpl implements GreetingActivities {
49+
50+
private static final Logger log = LoggerFactory.getLogger(GreetingActivitiesImpl.class);
51+
52+
@Override
53+
public String composeGreeting(String greeting, String name) {
54+
log.info("Composing greeting...");
55+
return greeting + ", " + name + "!";
56+
}
57+
}
58+
59+
public static void main(String[] args) {
60+
// Load configuration from environment and files.
61+
ClientConfigProfile profile;
62+
try {
63+
profile = ClientConfigProfile.load();
64+
} catch (IOException e) {
65+
throw new RuntimeException("Failed to load client configuration", e);
66+
}
67+
68+
// gRPC stubs wrapper that talks to the temporal service.
69+
WorkflowServiceStubs service =
70+
WorkflowServiceStubs.newServiceStubs(profile.toWorkflowServiceStubsOptions());
71+
72+
// WorkflowClient is required to create a Worker.
73+
WorkflowClient workflowClient =
74+
WorkflowClient.newInstance(service, profile.toWorkflowClientOptions());
75+
76+
// Worker factory that can be used to create workers for specific task queues.
77+
WorkerFactory factory = WorkerFactory.newInstance(workflowClient);
78+
79+
// Worker that listens on a task queue and hosts activity implementations.
80+
Worker worker = factory.newWorker(TASK_QUEUE);
81+
82+
// Activities are stateless and thread safe. So a shared instance is used.
83+
worker.registerActivitiesImplementations(new GreetingActivitiesImpl());
84+
85+
// Start listening to the activity task queue.
86+
factory.start();
87+
88+
// ActivityClient executes standalone activities directly from application code,
89+
// without a Workflow.
90+
ActivityClient client =
91+
ActivityClient.newInstance(
92+
service,
93+
ActivityClientOptions.newBuilder().setNamespace(profile.getNamespace()).build());
94+
95+
// Options specifying the activity ID, task queue, and timeout.
96+
StartActivityOptions options =
97+
StartActivityOptions.newBuilder()
98+
.setId(ACTIVITY_ID)
99+
.setTaskQueue(TASK_QUEUE)
100+
.setStartToCloseTimeout(Duration.ofSeconds(10))
101+
.build();
102+
103+
try {
104+
// Execute the activity and wait for its result. The typed API uses an unbound method
105+
// reference so the SDK can infer the activity type name and result type automatically.
106+
String result =
107+
client.execute(
108+
GreetingActivities.class,
109+
GreetingActivities::composeGreeting,
110+
options,
111+
"Hello",
112+
"World");
113+
114+
System.out.println(result);
115+
} finally {
116+
// Shut down the worker before the service so polling threads stop cleanly.
117+
factory.shutdown();
118+
service.shutdown();
119+
}
120+
}
121+
}

core/src/main/java/io/temporal/samples/hello/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ To run each hello world sample, use one of the following commands:
3535
./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloUpdate
3636
./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloSignalWithTimer
3737
./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloSignalWithStartAndWorkflowInit
38+
./gradlew -q execute -PmainClass=io.temporal.samples.hello.HelloStandaloneActivity
3839
```
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.temporal.samples.standaloneactivities;
2+
3+
import static io.temporal.samples.standaloneactivities.StandaloneActivityWorker.TASK_QUEUE;
4+
5+
import io.temporal.client.ActivityClient;
6+
import io.temporal.client.ActivityClientOptions;
7+
import io.temporal.client.ActivityExecutionCount;
8+
import io.temporal.envconfig.ClientConfigProfile;
9+
import io.temporal.serviceclient.WorkflowServiceStubs;
10+
import java.io.IOException;
11+
12+
/** Counts standalone activity executions on the task queue. */
13+
public class CountActivities {
14+
15+
public static void main(String[] args) throws IOException {
16+
ClientConfigProfile profile = ClientConfigProfile.load();
17+
WorkflowServiceStubs service =
18+
WorkflowServiceStubs.newServiceStubs(profile.toWorkflowServiceStubsOptions());
19+
20+
ActivityClient client =
21+
ActivityClient.newInstance(
22+
service,
23+
ActivityClientOptions.newBuilder().setNamespace(profile.getNamespace()).build());
24+
25+
try {
26+
ActivityExecutionCount resp = client.countExecutions("TaskQueue = '" + TASK_QUEUE + "'");
27+
28+
System.out.println("Total activities: " + resp.getCount());
29+
resp.getGroups()
30+
.forEach(
31+
group ->
32+
System.out.println("Group " + group.getGroupValues() + ": " + group.getCount()));
33+
} finally {
34+
service.shutdown();
35+
}
36+
}
37+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.temporal.samples.standaloneactivities;
2+
3+
import static io.temporal.samples.standaloneactivities.StandaloneActivityWorker.TASK_QUEUE;
4+
5+
import io.temporal.client.ActivityClient;
6+
import io.temporal.client.ActivityClientOptions;
7+
import io.temporal.client.StartActivityOptions;
8+
import io.temporal.envconfig.ClientConfigProfile;
9+
import io.temporal.serviceclient.WorkflowServiceStubs;
10+
import java.io.IOException;
11+
import java.time.Duration;
12+
13+
/**
14+
* Executes a standalone activity and waits for the result. Requires a Worker running
15+
* StandaloneActivityWorker.
16+
*/
17+
public class ExecuteActivity {
18+
19+
static final String ACTIVITY_ID = "standalone-activity-id";
20+
21+
public static void main(String[] args) throws IOException {
22+
ClientConfigProfile profile = ClientConfigProfile.load();
23+
WorkflowServiceStubs service =
24+
WorkflowServiceStubs.newServiceStubs(profile.toWorkflowServiceStubsOptions());
25+
26+
ActivityClient client =
27+
ActivityClient.newInstance(
28+
service,
29+
ActivityClientOptions.newBuilder().setNamespace(profile.getNamespace()).build());
30+
31+
StartActivityOptions options =
32+
StartActivityOptions.newBuilder()
33+
.setId(ACTIVITY_ID)
34+
.setTaskQueue(TASK_QUEUE)
35+
.setStartToCloseTimeout(Duration.ofSeconds(10))
36+
.build();
37+
38+
try {
39+
String result =
40+
client.execute(
41+
GreetingActivities.class,
42+
GreetingActivities::composeGreeting,
43+
options,
44+
"Hello",
45+
"World");
46+
System.out.println("Activity result: " + result);
47+
} finally {
48+
service.shutdown();
49+
}
50+
}
51+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.temporal.samples.standaloneactivities;
2+
3+
import io.temporal.activity.ActivityInterface;
4+
import io.temporal.activity.ActivityMethod;
5+
6+
/** Activity interface shared by all programs in this sample. */
7+
@ActivityInterface
8+
public interface GreetingActivities {
9+
10+
@ActivityMethod
11+
String composeGreeting(String greeting, String name);
12+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.temporal.samples.standaloneactivities;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
/** Activity implementation. */
7+
public class GreetingActivitiesImpl implements GreetingActivities {
8+
9+
private static final Logger log = LoggerFactory.getLogger(GreetingActivitiesImpl.class);
10+
11+
@Override
12+
public String composeGreeting(String greeting, String name) {
13+
log.info("Composing greeting...");
14+
return greeting + ", " + name + "!";
15+
}
16+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.temporal.samples.standaloneactivities;
2+
3+
import static io.temporal.samples.standaloneactivities.StandaloneActivityWorker.TASK_QUEUE;
4+
5+
import io.temporal.client.ActivityClient;
6+
import io.temporal.client.ActivityClientOptions;
7+
import io.temporal.client.ActivityExecutionMetadata;
8+
import io.temporal.envconfig.ClientConfigProfile;
9+
import io.temporal.serviceclient.WorkflowServiceStubs;
10+
import java.io.IOException;
11+
import java.util.stream.Stream;
12+
13+
/** Lists standalone activity executions on the task queue. */
14+
public class ListActivities {
15+
16+
public static void main(String[] args) throws IOException {
17+
ClientConfigProfile profile = ClientConfigProfile.load();
18+
WorkflowServiceStubs service =
19+
WorkflowServiceStubs.newServiceStubs(profile.toWorkflowServiceStubsOptions());
20+
21+
ActivityClient client =
22+
ActivityClient.newInstance(
23+
service,
24+
ActivityClientOptions.newBuilder().setNamespace(profile.getNamespace()).build());
25+
26+
try (Stream<ActivityExecutionMetadata> activities =
27+
client.listExecutions("TaskQueue = '" + TASK_QUEUE + "'")) {
28+
activities.forEach(
29+
info ->
30+
System.out.printf(
31+
"ActivityID: %s, Type: %s, Status: %s%n",
32+
info.getActivityId(), info.getActivityType(), info.getStatus()));
33+
} finally {
34+
service.shutdown();
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)