Skip to content

Commit 0c8586b

Browse files
donald-pinckneyclaudebrianstrauch
authored
Add Java SDK support (#42)
* Add Java SDK reference files (11 files) Create complete Java reference documentation covering: - java.md: Entry point with quick start tutorial, key concepts - patterns.md: 17 patterns (signals, queries, updates, child workflows, saga, cancellation scopes, heartbeating, etc.) - determinism.md: Safe alternatives table, forbidden operations - determinism-protection.md: Convention-based enforcement (no sandbox) - error-handling.md: ApplicationFailure, retry/timeout config - gotchas.md: Non-deterministic operations, cancellation, heartbeating - testing.md: TestWorkflowEnvironment, Mockito mocking, replay testing - versioning.md: Workflow.getVersion(), worker versioning - data-handling.md: Jackson, PayloadConverter, encryption, search attributes - observability.md: SLF4J logging, Micrometer metrics - advanced-features.md: Schedules, async completion, worker tuning Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix Java alignment issues from self-review - Reduce gotchas.md Non-Deterministic Operations from ~94 lines to ~12 (reference determinism.md instead of duplicating) - Remove Workflow Failure Exception Types duplication from error-handling.md (keep only in advanced-features.md) - Expand versioning.md Worker Versioning with Key Concepts, PINNED vs AUTO_UPGRADE, Deployment Strategies subsections - Fix section names to match Python reference style: Activity Heartbeat Details, Handling Activity Errors, Retry Policy Configuration, Workflow Test Environment, Mocking Activities, Workflow Replay Testing - Reduce data-handling.md Payload Encryption verbosity - Reduce observability.md Logger Customization verbosity - Reduce testing.md to single approach per section - Rename determinism.md "Convention-Based Enforcement" to "SDK Protection" - Fix handler guidance in patterns.md to match Python Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix correctness issues in Java reference files - patterns.md: Fix Queries section — ActivityStub → typed interface (Workflow.newActivityStub returns the typed interface, not ActivityStub) - data-handling.md: Add missing ProtobufPayloadConverter to default converter chain (4th of 5 converters) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add Java to SKILL.md and core/determinism.md - SKILL.md: Add "Temporal Java" trigger phrase, update Overview to list Java, add Java entry to Getting Started references - core/determinism.md: Add Java entry to SDK Protection Mechanisms (no sandbox, convention-based, NonDeterministicException at replay) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Apply manual editorial fixes to Java references - java.md: Remove "Understanding Replay" section (covered by Overview), simplify File Organization note (no sandbox rationale) - gotchas.md: Move Heartbeating before Cancellation, make Wrong Retry Classification brief with reference (not inline examples) - error-handling.md: Remove editorializing from Workflow Failure note - determinism-protection.md: Remove cross-language comparison paragraph (state Java's approach on its own terms) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add temporal-workflowcheck static analysis to Java determinism docs - determinism-protection.md: Add "Static Analysis with temporal-workflowcheck" section with Gradle/Maven setup, manual run, and suppression instructions. Beta warning included. - determinism.md: Update overview and SDK Protection to reference workflowcheck - core/determinism.md: Update Java entry in SDK Protection Mechanisms Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Integrate feedback from Go PR into Java patterns - Updates: Add validator note — validators must not mutate state or block (matches note added to Python, TypeScript, Go, and core) - Saga Pattern: Use Workflow.newDetachedCancellationScope() for compensations so they execute even if the workflow is cancelled (mirrors Go's workflow.NewDisconnectedContext pattern) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs: add @WorkflowInit description to java.md Key Concepts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * mark java as supported * Apply suggestions from code review Co-authored-by: Brian Strauch <brian@brianstrauch.com> * strongly recommend java 21+ * Softened stance on static checker and replay testing. * address python/typescript sandboxing comment --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Brian Strauch <brian.strauch@temporal.io> Co-authored-by: Brian Strauch <brian@brianstrauch.com>
1 parent 9b97cee commit 0c8586b

14 files changed

Lines changed: 2321 additions & 4 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Appropriately adjust the installation directory based on your coding agent.
3636
- [x] Python ✅
3737
- [x] TypeScript ✅
3838
- [x] Go ✅
39-
- [ ] Java 🚧 ([PR](https://github.com/temporalio/skill-temporal-developer/pull/42))
39+
- [x] Java
4040
- [ ] .NET 🚧 ([PR](https://github.com/temporalio/skill-temporal-developer/pull/39))
4141
- [ ] Ruby 🚧 ([PR](https://github.com/temporalio/skill-temporal-developer/pull/41))
4242
- [ ] PHP 🚧 ([PR](https://github.com/temporalio/skill-temporal-developer/pull/40))

SKILL.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
---
22
name: temporal-developer
3-
description: This skill should be used when the user asks to "create a Temporal workflow", "write a Temporal activity", "debug stuck workflow", "fix non-determinism error", "Temporal Python", "Temporal TypeScript", "Temporal Go", "Temporal Golang", "workflow replay", "activity timeout", "signal workflow", "query workflow", "worker not starting", "activity keeps retrying", "Temporal heartbeat", "continue-as-new", "child workflow", "saga pattern", "workflow versioning", "durable execution", "reliable distributed systems", or mentions Temporal SDK development.
3+
description: This skill should be used when the user asks to "create a Temporal workflow", "write a Temporal activity", "debug stuck workflow", "fix non-determinism error", "Temporal Python", "Temporal TypeScript", "Temporal Go", "Temporal Golang", "Temporal Java", "workflow replay", "activity timeout", "signal workflow", "query workflow", "worker not starting", "activity keeps retrying", "Temporal heartbeat", "continue-as-new", "child workflow", "saga pattern", "workflow versioning", "durable execution", "reliable distributed systems", or mentions Temporal SDK development.
44
version: 0.1.0
55
---
66

77
# Skill: temporal-developer
88

99
## Overview
1010

11-
Temporal is a durable execution platform that makes workflows survive failures automatically. This skill provides guidance for building Temporal applications in Python, TypeScript, and Go.
11+
Temporal is a durable execution platform that makes workflows survive failures automatically. This skill provides guidance for building Temporal applications in Python, TypeScript, Go, and Java.
1212

1313
## Core Architecture
1414

@@ -79,6 +79,7 @@ Once you've downloaded the file, extract the downloaded archive and add the temp
7979
1. First, read the getting started guide for the language you are working in:
8080
- Python -> read `references/python/python.md`
8181
- TypeScript -> read `references/typescript/typescript.md`
82+
- Java -> read `references/java/java.md`
8283
- Go -> read `references/go/go.md`
8384
2. Second, read appropriate `core` and language-specific references for the task at hand.
8485

references/core/determinism.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@ In Temporal, activities are the primary mechanism for making non-deterministic c
7676
For a few simple cases, like timestamps, random values, UUIDs, etc. the Temporal SDK in your language may provide durable variants that are simple to use. See `references/{your_language}/determinism.md` for the language you are working in for more info.
7777

7878
## SDK Protection Mechanisms
79-
Each Temporal SDK language provides a protection mechanism to make it easier to catch non-determinism errors earlier in development:
79+
Each Temporal SDK language provides a different level of protection against non-determinism:
8080

8181
- Python: The Python SDK runs workflows in a sandbox that intercepts and aborts non-deterministic calls early at runtime.
8282
- TypeScript: The TypeScript SDK runs workflows in an isolated V8 sandbox, intercepting many common sources of non-determinism and replacing them automatically with deterministic variants.
83+
- Java: The Java SDK has no sandbox. Determinism is enforced by developer conventions — the SDK provides `Workflow.*` APIs as safe alternatives (e.g., `Workflow.sleep()` instead of `Thread.sleep()`), and non-determinism is only detected at replay time via `NonDeterministicException`. A static analysis tool (`temporal-workflowcheck`, beta) can catch violations at build time. Cooperative threading under a global lock eliminates the need for synchronization.
8384
- Go: The Go SDK has no runtime sandbox. Therefore, non-determinism bugs will never be immediately appararent, and are usually only observable during replay. The optional `workflowcheck` static analysis tool can be used to check for many sources of non-determinism at compile time.
8485

8586
Regardless of which SDK you are using, it is your responsibility to ensure that workflow code does not contain sources of non-determinism. Use SDK-specific tools as well as replay tests for doing so.
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Java SDK Advanced Features
2+
3+
## Schedules
4+
5+
Create recurring workflow executions.
6+
7+
```java
8+
import io.temporal.client.schedules.*;
9+
10+
ScheduleClient scheduleClient = ScheduleClient.newInstance(service);
11+
12+
// Create a schedule
13+
String scheduleId = "daily-report";
14+
ScheduleHandle handle = scheduleClient.createSchedule(
15+
scheduleId,
16+
Schedule.newBuilder()
17+
.setAction(
18+
ScheduleActionStartWorkflow.newBuilder()
19+
.setWorkflowType(DailyReportWorkflow.class)
20+
.setOptions(
21+
WorkflowOptions.newBuilder()
22+
.setWorkflowId("daily-report")
23+
.setTaskQueue("reports")
24+
.build()
25+
)
26+
.build()
27+
)
28+
.setSpec(
29+
ScheduleSpec.newBuilder()
30+
.setIntervals(
31+
List.of(new ScheduleIntervalSpec(Duration.ofDays(1)))
32+
)
33+
.build()
34+
)
35+
.build(),
36+
ScheduleOptions.newBuilder().build()
37+
);
38+
39+
// Manage schedules
40+
ScheduleHandle scheduleHandle = scheduleClient.getHandle(scheduleId);
41+
scheduleHandle.pause("Maintenance window");
42+
scheduleHandle.unpause();
43+
scheduleHandle.trigger(); // Run immediately
44+
scheduleHandle.delete();
45+
```
46+
47+
## Async Activity Completion
48+
49+
For activities that complete asynchronously (e.g., human tasks, external callbacks).
50+
If you configure a heartbeat timeout on this activity, the external completer is responsible for sending heartbeats via the async handle.
51+
52+
**Note:** If the external system can reliably Signal back with the result and doesn't need to Heartbeat or receive Cancellation, consider using **signals** instead.
53+
54+
```java
55+
public class ApprovalActivitiesImpl implements ApprovalActivities {
56+
@Override
57+
public String requestApproval(String requestId) {
58+
ActivityExecutionContext ctx = Activity.getExecutionContext();
59+
60+
// Get task token for async completion
61+
byte[] taskToken = ctx.getTaskToken();
62+
63+
// Store task token for later completion (e.g., in database)
64+
storeTaskToken(requestId, taskToken);
65+
66+
// Mark this activity as waiting for external completion
67+
ctx.doNotCompleteOnReturn();
68+
69+
return null; // Return value is ignored
70+
}
71+
}
72+
73+
// Later, complete the activity from another process
74+
public void completeApproval(String requestId, boolean approved) {
75+
WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
76+
WorkflowClient client = WorkflowClient.newInstance(service);
77+
78+
ActivityCompletionClient completionClient = client.newActivityCompletionClient();
79+
80+
byte[] taskToken = getTaskToken(requestId);
81+
82+
if (approved) {
83+
completionClient.complete(taskToken, "approved");
84+
} else {
85+
completionClient.completeExceptionally(
86+
taskToken,
87+
new RuntimeException("Rejected")
88+
);
89+
}
90+
}
91+
```
92+
93+
## Worker Tuning
94+
95+
Configure worker performance settings.
96+
97+
```java
98+
WorkerOptions workerOptions = WorkerOptions.newBuilder()
99+
// Max concurrent workflow task executions (default: 200)
100+
.setMaxConcurrentWorkflowTaskExecutionSize(200)
101+
// Max concurrent activity executions (default: 200)
102+
.setMaxConcurrentActivityExecutionSize(200)
103+
// Max concurrent local activity executions (default: 200)
104+
.setMaxConcurrentLocalActivityExecutionSize(200)
105+
// Max workflow task pollers (default: 5)
106+
.setMaxConcurrentWorkflowTaskPollers(5)
107+
// Max activity task pollers (default: 5)
108+
.setMaxConcurrentActivityTaskPollers(5)
109+
.build();
110+
111+
WorkerFactory factory = WorkerFactory.newInstance(client);
112+
Worker worker = factory.newWorker("my-queue", workerOptions);
113+
worker.registerWorkflowImplementationTypes(MyWorkflowImpl.class);
114+
worker.registerActivitiesImplementations(new MyActivitiesImpl());
115+
factory.start();
116+
```
117+
118+
## Workflow Failure Exception Types
119+
120+
Control which exceptions cause workflow failures vs workflow task failures.
121+
122+
By default, only `ApplicationFailure` (and its subclasses) fail the workflow execution. All other exceptions fail the **workflow task**, causing the task to retry indefinitely until the code is fixed or the workflow is terminated.
123+
124+
### Per-Workflow Configuration
125+
126+
Use `WorkflowImplementationOptions` to specify which exception types should fail the workflow:
127+
128+
```java
129+
Worker worker = factory.newWorker("my-queue");
130+
worker.registerWorkflowImplementationTypes(
131+
WorkflowImplementationOptions.newBuilder()
132+
.setFailWorkflowExceptionTypes(
133+
IllegalArgumentException.class,
134+
CustomBusinessException.class
135+
)
136+
.build(),
137+
MyWorkflowImpl.class
138+
);
139+
```
140+
141+
With this configuration, `IllegalArgumentException` and `CustomBusinessException` thrown from the workflow will fail the workflow execution instead of just the workflow task.
142+
143+
### Worker-Level Configuration
144+
145+
Apply to all workflows registered on the worker:
146+
147+
```java
148+
WorkerFactoryOptions factoryOptions = WorkerFactoryOptions.newBuilder()
149+
.setWorkflowHostLocalTaskQueueScheduleToStartTimeout(Duration.ofSeconds(10))
150+
.build();
151+
WorkerFactory factory = WorkerFactory.newInstance(client, factoryOptions);
152+
153+
Worker worker = factory.newWorker("my-queue");
154+
// Register each workflow type with its own failure exception types
155+
worker.registerWorkflowImplementationTypes(
156+
WorkflowImplementationOptions.newBuilder()
157+
.setFailWorkflowExceptionTypes(
158+
IllegalArgumentException.class,
159+
CustomBusinessException.class
160+
)
161+
.build(),
162+
MyWorkflowImpl.class,
163+
AnotherWorkflowImpl.class
164+
);
165+
```
166+
167+
- **Tip for testing:** Set `setFailWorkflowExceptionTypes(Throwable.class)` so any unhandled exception fails the workflow immediately rather than retrying the workflow task forever. This surfaces bugs faster.

0 commit comments

Comments
 (0)