This document provides comprehensive guidance for AI assistants (like Claude, GitHub Copilot, or ChatGPT) working with this Temporal Java SDK project template.
This is a Temporal Java SDK project template designed for building reliable, distributed applications using modern Java practices. The template demonstrates core Temporal patterns through two practical workflows (HTTP and Crawler) while maintaining production-grade code quality standards.
Primary Use Case: Starting point for AI-enabled workflow development in Java, with emphasis on:
- Type-safe workflow orchestration
- Comprehensive testing patterns
- Enterprise-grade code quality
- Spring Boot integration
Understanding these technologies is essential for working with this codebase:
- Temporal SDK (1.25.2): Orchestration engine for building resilient distributed systems
- Java 21: LTS version with modern language features (records, pattern matching, virtual threads)
- Spring Boot 3.3.6: Application framework and dependency injection
- Gradle 8.11.1: Build automation with Groovy DSL
- Jackson: JSON serialization/deserialization (Temporal's default)
- Java Records: Immutable data models for workflow inputs/outputs
- RestTemplate: HTTP client for activities
- Jakarta Validation: Input validation annotations
- Checkstyle: Google Java Style enforcement
- SpotBugs: Static analysis for bug detection
- Spotless: Automatic code formatting (Google Java Format)
- JaCoCo: Code coverage reporting (80% minimum)
- JUnit 5: Modern testing framework
- Mockito: Mocking framework for unit tests
- TestWorkflowEnvironment: Temporal's in-memory testing with time-skipping
- Lombok: Reduce boilerplate (used sparingly, prefer records)
- Pre-commit: Git hooks for code quality
- Gradle Toolchains: Automatic JDK provisioning
temporal-java-template/
βββ src/
β βββ main/
β β βββ java/com/example/temporal/
β β β βββ TemporalApplication.java # Spring Boot entry point
β β β βββ config/
β β β β βββ TemporalConfig.java # Temporal client beans
β β β βββ workflows/
β β β βββ http/ # HTTP workflow package
β β β β βββ HttpWorkflow.java # Workflow interface
β β β β βββ HttpWorkflowImpl.java # Workflow implementation
β β β β βββ HttpActivities.java # Activity interface
β β β β βββ HttpActivitiesImpl.java # Activity implementation
β β β β βββ HttpWorker.java # Standalone worker
β β β β βββ *.java # Data models (records)
β β β βββ crawler/ # Crawler workflow package
β β β βββ [similar structure]
β β βββ resources/
β β βββ application.yml # Configuration
β βββ test/
β βββ java/com/example/temporal/
β βββ TestConfig.java # Test environment setup
β βββ workflows/
β βββ http/
β β βββ HttpActivitiesTest.java # Activity unit tests
β β βββ HttpWorkflowTest.java # Workflow integration tests
β βββ crawler/
β βββ [similar test structure]
βββ config/
β βββ checkstyle/checkstyle.xml # Code style rules
β βββ spotbugs/exclude.xml # Static analysis exclusions
βββ docs/
β βββ temporal-patterns.md # Temporal patterns guide
β βββ testing.md # Testing guide
βββ build.gradle # Gradle build configuration
βββ gradle.properties # Build properties
βββ settings.gradle # Gradle settings
βββ .pre-commit-config.yaml # Pre-commit hooks
βββ README.md # User documentation
βββ CLAUDE.md # Claude Code assistant guide
βββ AGENTS.md # This file
-
Workflows (
*Workflow.java,*WorkflowImpl.java)- Orchestrate business logic
- Must be deterministic (same inputs β same outputs)
- Cannot perform I/O directly
- Use
Workflow.*methods for time, random, logging - Serialized and replayed during execution
-
Activities (
*Activities.java,*ActivitiesImpl.java)- Execute non-deterministic operations (HTTP, database, file I/O)
- Can fail and retry automatically
- Injected with Spring beans (RestTemplate, repositories, etc.)
- Use standard logging (
LoggerFactory.getLogger())
-
Workers (
*Worker.java)- Poll task queues and execute workflows/activities
- Run separately from Spring Boot application
- Configure thread pools for concurrent execution
- Production workers often run in separate processes/containers
-
Data Models (Java Records)
- Immutable, serializable workflow inputs/outputs
- Type-safe contract between workflow and client
- Jackson-compatible by default
β ALLOWED in Workflows:
Workflow.currentTimeMillis() // Deterministic time
Workflow.newRandom() // Deterministic random
Workflow.getLogger() // Replay-safe logging
Workflow.sleep(Duration) // Workflow timers
Workflow.await(() -> condition) // Wait for signals
activities.doSomething() // Activity calls
Async.function(activities::method) // Parallel executionβ FORBIDDEN in Workflows:
System.currentTimeMillis() // Non-deterministic!
Math.random() // Non-deterministic!
new Random().nextInt() // Non-deterministic!
LoggerFactory.getLogger() // Wrong logger!
Thread.sleep() // Blocks workflow!
restTemplate.getForEntity() // Direct I/O forbidden!
repository.save() // Direct I/O forbidden!All code must pass these checks before commit:
-
Spotless Check (
./gradlew spotlessCheck)- Google Java Format (2-space indentation)
- Import ordering
- Trailing whitespace removal
- Fix automatically:
./gradlew spotlessApply
-
Checkstyle (
./gradlew checkstyleMain checkstyleTest)- Google Java Style with modifications
- 2-space indentation
- Lowercase
loggerconstant allowed - Test methods can use underscores:
test_method_scenario()
-
SpotBugs (
./gradlew spotbugsMain)- Static analysis for common bugs
- Excludes: EI_EXPOSE_REP for Temporal patterns
- View report:
build/reports/spotbugs/main/spotbugs.html
-
Test Coverage (
./gradlew test jacocoTestReport)- Minimum 80% code coverage
- Both unit tests (activities) and integration tests (workflows)
- View report:
build/reports/jacoco/test/html/index.html
Data Models - Prefer Records:
// β
GOOD: Immutable record
public record HttpWorkflowInput(String url) {}
public record HttpWorkflowOutput(
String responseText,
String url,
int statusCode
) {}Activity Interface & Implementation:
// Interface
@ActivityInterface
public interface MyActivities {
MyOutput doSomething(MyInput input);
}
// Implementation (Spring component)
@Component
public class MyActivitiesImpl implements MyActivities {
private static final Logger logger = LoggerFactory.getLogger(MyActivitiesImpl.class);
private final RestTemplate restTemplate;
public MyActivitiesImpl(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public MyOutput doSomething(MyInput input) {
logger.info("Processing: {}", input);
// Implementation with I/O operations
return new MyOutput("result");
}
}Workflow Interface & Implementation:
// Interface
@WorkflowInterface
public interface MyWorkflow {
@WorkflowMethod
MyOutput run(MyInput input);
}
// Implementation
public class MyWorkflowImpl implements MyWorkflow {
private static final Logger logger = Workflow.getLogger(MyWorkflowImpl.class);
private final ActivityOptions activityOptions =
ActivityOptions.newBuilder()
.setStartToCloseTimeout(Duration.ofSeconds(10))
.build();
private final MyActivities activities =
Workflow.newActivityStub(MyActivities.class, activityOptions);
@Override
public MyOutput run(MyInput input) {
logger.info("Starting workflow with input: {}", input);
return activities.doSomething(input);
}
}Worker:
public class MyWorker {
public static final String TASK_QUEUE = "my-task-queue";
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(MyWorkflowImpl.class);
worker.registerActivitiesImplementations(new MyActivitiesImpl());
factory.start();
logger.info("Worker started for task queue: {}", TASK_QUEUE);
}
}Unit Test (Activities):
@ExtendWith(MockitoExtension.class)
class MyActivitiesTest {
@Mock private RestTemplate restTemplate;
private MyActivitiesImpl activities;
@BeforeEach
void setUp() {
activities = new MyActivitiesImpl(restTemplate);
}
@Test
void testDoSomething_Success() {
// Arrange
when(restTemplate.getForEntity(anyString(), eq(String.class)))
.thenReturn(new ResponseEntity<>("response", HttpStatus.OK));
// Act
MyOutput output = activities.doSomething(new MyInput("test"));
// Assert
assertNotNull(output);
verify(restTemplate).getForEntity(anyString(), eq(String.class));
}
}Integration Test (Workflows):
class MyWorkflowTest {
private TestWorkflowEnvironment testEnv;
private Worker worker;
private WorkflowClient client;
@BeforeEach
void setUp() {
testEnv = TestWorkflowEnvironment.newInstance();
worker = testEnv.newWorker(MyWorker.TASK_QUEUE);
worker.registerWorkflowImplementationTypes(MyWorkflowImpl.class);
client = testEnv.getWorkflowClient();
}
@AfterEach
void tearDown() {
testEnv.close();
}
@Test
void testWorkflow_Success() {
// Mock activities
MyActivities mockActivities = mock(MyActivities.class);
when(mockActivities.doSomething(any())).thenReturn(new MyOutput("mocked"));
worker.registerActivitiesImplementations(mockActivities);
testEnv.start();
// Execute workflow
MyWorkflow workflow = client.newWorkflowStub(
MyWorkflow.class,
WorkflowOptions.newBuilder().setTaskQueue(MyWorker.TASK_QUEUE).build());
MyOutput output = workflow.run(new MyInput("test"));
// Assert
assertEquals("mocked", output.result());
verify(mockActivities).doSomething(any());
}
}-
Understand Temporal Concepts
- Workflows orchestrate, activities execute I/O
- Workflows must be deterministic (no direct I/O, use
Workflow.*methods) - Activities can fail and retry based on retry policies
- Workers run separately from the Spring Boot application
-
Follow Existing Patterns
- Use Java records for data models (not POJOs with Lombok)
- Activities are Spring components with dependency injection
- Workflows have NO Spring dependencies (they're serialized/replayed)
- Package by feature: each workflow lives in its own package
-
Code Organization
- Each workflow gets its own package under
workflows/ - Package includes: interfaces, implementations, data models, worker, tests
- Data models use records with validation annotations if needed
- Workers are standalone main classes (run separately)
- Each workflow gets its own package under
-
Testing Requirements
- Write unit tests for activities (mock external dependencies)
- Write integration tests for workflows (mock activities)
- Use
TestWorkflowEnvironmentfor time-skipping tests - Maintain 80% code coverage minimum
- Test both success and failure scenarios
-
Error Handling
- Activities throw exceptions that trigger retries
- Configure retry policies in
ActivityOptions - Non-retryable exceptions: use
setDoNotRetry() - Workflows handle
ActivityFailurefor compensation logic
-
Documentation
- Update README.md for new workflows
- Add examples to
docs/temporal-patterns.md - Include test examples in
docs/testing.md - Document complex business logic in workflow comments
Adding a New Workflow:
- Create package:
src/main/java/com/example/temporal/workflows/myworkflow/ - Create data models (records):
MyWorkflowInput,MyWorkflowOutput, etc. - Create activity interface:
MyActivities.javawith@ActivityInterface - Create activity implementation:
MyActivitiesImpl.javaas Spring@Component - Create workflow interface:
MyWorkflow.javawith@WorkflowInterfaceand@WorkflowMethod - Create workflow implementation:
MyWorkflowImpl.java(no Spring annotations!) - Create worker:
MyWorker.javawith main method and task queue constant - Create tests:
MyActivitiesTest.java(unit) andMyWorkflowTest.java(integration) - Update README.md with usage examples
- Run quality checks:
./gradlew qualityCheck
Adding Dependencies:
Edit build.gradle:
dependencies {
implementation 'group:artifact:version'
testImplementation 'group:test-artifact:version'
}Then sync: ./gradlew build --refresh-dependencies
Running Workflows Locally:
- Start Temporal Server:
temporal server start-dev - Run worker:
./gradlew runMyWorker(add task to build.gradle) - Execute workflow via CLI or Java client
- View workflow in Temporal Web UI: http://localhost:8233
Code Formatting:
# Check formatting
./gradlew spotlessCheck
# Auto-fix formatting
./gradlew spotlessApply
# Check style
./gradlew checkstyleMain checkstyleTest
# Run static analysis
./gradlew spotbugsMain
# Run all quality checks
./gradlew qualityCheck-
Never inject Spring beans into workflows
- Workflows are serialized and replayed
- Only activity stubs can be used in workflows
- Move all Spring dependencies to activities
-
Always use Workflow. methods in workflows*
- Time:
Workflow.currentTimeMillis(),Workflow.sleep() - Random:
Workflow.newRandom() - Logging:
Workflow.getLogger() - Async:
Async.function(),Promise.allOf()
- Time:
-
Activity timeouts are critical
- Always set
StartToCloseTimeout - Consider
HeartbeatTimeoutfor long activities - Configure retry policies for transient failures
- Always set
-
Testing is different from traditional Spring Boot
- Use
TestWorkflowEnvironment, not@SpringBootTestfor workflows - Mock activities, not external services in workflow tests
- Time-skipping allows testing long-running workflows instantly
- Use
- Gradle 8.11.1 with Groovy DSL (not Kotlin DSL)
- Java 21 Toolchain: Auto-provisioned via Foojay Resolver
- Build tasks:
build,test,qualityCheck,spotlessApply - Worker tasks:
runHttpWorker,runCrawlerWorker(JavaExec tasks)
- Source:
src/main/java/com/example/temporal/ - Tests:
src/test/java/com/example/temporal/ - Resources:
src/main/resources/(application.yml) - Config:
config/checkstyle/,config/spotbugs/ - Docs:
docs/(patterns, testing guides)
- β Don't use
System.currentTimeMillis()in workflows β UseWorkflow.currentTimeMillis() - β Don't inject Spring beans into workflow implementations β Use activities instead
- β Don't use
Thread.sleep()in workflows β UseWorkflow.sleep() - β Don't use standard logger in workflows β Use
Workflow.getLogger() - β Don't forget activity timeouts β Always set
StartToCloseTimeout - β Don't test workflows with real activities β Mock activities in workflow tests
- β Don't skip code quality checks β Run
./gradlew qualityCheckbefore commit - β Don't modify workflow code without versioning β Use
Workflow.getVersion()for changes
- Temporal Java SDK Documentation
- Temporal Best Practices
- Spring Boot Documentation
- Project README - User-facing documentation
- CLAUDE.md - Claude Code assistant guide
- docs/temporal-patterns.md - Pattern reference
- docs/testing.md - Testing guide
This project is licensed under the MIT License - see the LICENSE file for details.