Files
Donald Pinckney 0c8586b4c2 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>
2026-04-02 17:07:33 -04:00

168 lines
5.7 KiB
Markdown

# Java SDK Advanced Features
## Schedules
Create recurring workflow executions.
```java
import io.temporal.client.schedules.*;
ScheduleClient scheduleClient = ScheduleClient.newInstance(service);
// Create a schedule
String scheduleId = "daily-report";
ScheduleHandle handle = scheduleClient.createSchedule(
scheduleId,
Schedule.newBuilder()
.setAction(
ScheduleActionStartWorkflow.newBuilder()
.setWorkflowType(DailyReportWorkflow.class)
.setOptions(
WorkflowOptions.newBuilder()
.setWorkflowId("daily-report")
.setTaskQueue("reports")
.build()
)
.build()
)
.setSpec(
ScheduleSpec.newBuilder()
.setIntervals(
List.of(new ScheduleIntervalSpec(Duration.ofDays(1)))
)
.build()
)
.build(),
ScheduleOptions.newBuilder().build()
);
// Manage schedules
ScheduleHandle scheduleHandle = scheduleClient.getHandle(scheduleId);
scheduleHandle.pause("Maintenance window");
scheduleHandle.unpause();
scheduleHandle.trigger(); // Run immediately
scheduleHandle.delete();
```
## Async Activity Completion
For activities that complete asynchronously (e.g., human tasks, external callbacks).
If you configure a heartbeat timeout on this activity, the external completer is responsible for sending heartbeats via the async handle.
**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.
```java
public class ApprovalActivitiesImpl implements ApprovalActivities {
@Override
public String requestApproval(String requestId) {
ActivityExecutionContext ctx = Activity.getExecutionContext();
// Get task token for async completion
byte[] taskToken = ctx.getTaskToken();
// Store task token for later completion (e.g., in database)
storeTaskToken(requestId, taskToken);
// Mark this activity as waiting for external completion
ctx.doNotCompleteOnReturn();
return null; // Return value is ignored
}
}
// Later, complete the activity from another process
public void completeApproval(String requestId, boolean approved) {
WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
WorkflowClient client = WorkflowClient.newInstance(service);
ActivityCompletionClient completionClient = client.newActivityCompletionClient();
byte[] taskToken = getTaskToken(requestId);
if (approved) {
completionClient.complete(taskToken, "approved");
} else {
completionClient.completeExceptionally(
taskToken,
new RuntimeException("Rejected")
);
}
}
```
## Worker Tuning
Configure worker performance settings.
```java
WorkerOptions workerOptions = WorkerOptions.newBuilder()
// Max concurrent workflow task executions (default: 200)
.setMaxConcurrentWorkflowTaskExecutionSize(200)
// Max concurrent activity executions (default: 200)
.setMaxConcurrentActivityExecutionSize(200)
// Max concurrent local activity executions (default: 200)
.setMaxConcurrentLocalActivityExecutionSize(200)
// Max workflow task pollers (default: 5)
.setMaxConcurrentWorkflowTaskPollers(5)
// Max activity task pollers (default: 5)
.setMaxConcurrentActivityTaskPollers(5)
.build();
WorkerFactory factory = WorkerFactory.newInstance(client);
Worker worker = factory.newWorker("my-queue", workerOptions);
worker.registerWorkflowImplementationTypes(MyWorkflowImpl.class);
worker.registerActivitiesImplementations(new MyActivitiesImpl());
factory.start();
```
## Workflow Failure Exception Types
Control which exceptions cause workflow failures vs workflow task failures.
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.
### Per-Workflow Configuration
Use `WorkflowImplementationOptions` to specify which exception types should fail the workflow:
```java
Worker worker = factory.newWorker("my-queue");
worker.registerWorkflowImplementationTypes(
WorkflowImplementationOptions.newBuilder()
.setFailWorkflowExceptionTypes(
IllegalArgumentException.class,
CustomBusinessException.class
)
.build(),
MyWorkflowImpl.class
);
```
With this configuration, `IllegalArgumentException` and `CustomBusinessException` thrown from the workflow will fail the workflow execution instead of just the workflow task.
### Worker-Level Configuration
Apply to all workflows registered on the worker:
```java
WorkerFactoryOptions factoryOptions = WorkerFactoryOptions.newBuilder()
.setWorkflowHostLocalTaskQueueScheduleToStartTimeout(Duration.ofSeconds(10))
.build();
WorkerFactory factory = WorkerFactory.newInstance(client, factoryOptions);
Worker worker = factory.newWorker("my-queue");
// Register each workflow type with its own failure exception types
worker.registerWorkflowImplementationTypes(
WorkflowImplementationOptions.newBuilder()
.setFailWorkflowExceptionTypes(
IllegalArgumentException.class,
CustomBusinessException.class
)
.build(),
MyWorkflowImpl.class,
AnotherWorkflowImpl.class
);
```
- **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.