Building Reliable Microservices with Temporal.io and Spring Boot
A practical guide to implementing durable workflows and fault-tolerant business processes using Temporal’s workflow engine with Spring Boot…
Building Reliable Microservices with Temporal.io and Spring Boot
A practical guide to implementing durable workflows and fault-tolerant business processes using Temporal’s workflow engine with Spring Boot applications
Understanding Temporal.io Core Concepts
Temporal.io is a workflow orchestration platform that addresses the challenges of building reliable distributed systems by providing durable execution, explicit failure handling, and state management.
At its core, Temporal.io tackles the fundamental challenges of distributed systems by introducing a paradigm where application code can be written as if failures don’t exist. The platform achieves this through a durable execution model that automatically handles retries, timeouts, and state recovery, allowing developers to focus on business logic rather than infrastructure concerns.
Workflows represent the central abstraction in Temporal, defining the orchestration of business processes as code. Unlike traditional approaches, Temporal workflows are deterministic programs that maintain their execution state even across process and machine failures. This state persistence enables workflows to pick up exactly where they left off after any interruption.
Activities are the building blocks that workflows coordinate — representing individual steps or tasks that interact with external systems. Activities are dispatched through task queues, which act as the communication layer between workflow orchestrators and activity workers. This decoupling enables independent scaling and deployment of different components within your architecture.
What distinguishes Temporal from message brokers or traditional orchestration tools is its stateful execution model. While message brokers simply deliver messages with no knowledge of application logic, and many orchestration tools use a centralized database for state, Temporal’s event-sourcing approach maintains complete workflow history that can be deterministically replayed.
The saga pattern — a sequence of transactions where each step has a corresponding compensating action — is elegantly implemented with Temporal. Workflows provide natural boundaries for sagas, with the platform handling the complex compensation logic automatically when failures occur at any point in the process chain.
Tips
- Start with defining simple workflows before implementing complex orchestration patterns
- Use workflow retries for transient failures and activity retries for operation-specific failures
- Leverage child workflows for modularizing complex business processes
- Remember that workflow code must be deterministic — avoid relying on external state that might change between executions
Setting Up Temporal Server for Development
This section walks through setting up a local Temporal server environment using Docker to support the development of microservices with Spring Boot integration.
Before developing Temporal workflows in your Spring Boot application, you need a running Temporal server environment. The simplest approach for development is using Docker, which provides a pre-configured Temporal server with all necessary components.
Docker Compose offers the most flexible setup for local development. Temporal provides an official Docker Compose file that starts the server along with essential dependencies like PostgreSQL for persistence and Elasticsearch for advanced visibility features.
Once your Temporal server is running, you’ll want to interact with it using the Temporal CLI (tctl). This command-line tool allows you to register namespaces, manage workflow executions, and perform administrative tasks. The CLI can be installed separately or used through the Docker container.
The Temporal Web UI is an essential development tool that provides visibility into workflow executions, task queues, and namespaces. Access it through your browser at http://localhost:8088 once your Docker containers are running. This interface allows you to monitor workflow execution history, inspect payloads, and troubleshoot workflow failures.
For a more production-like development experience, you’ll want to configure persistent storage instead of the default ephemeral storage. This ensures your workflow history and state survive container restarts, which is essential for testing long-running workflows and recovery scenarios.
version: '3'
services:
temporal:
image: temporalio/auto-setup:1.21.0
ports:
- "7233:7233" # gRPC service
- "8088:8088" # Web UI
environment:
- "DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development.yaml"
- "PERSISTENCE_TYPE=sql"
- "DB=postgresql"
- "DB_PORT=5432"
- "POSTGRES_USER=temporal"
- "POSTGRES_PWD=temporal"
- "POSTGRES_SEEDS=postgresql"
depends_on:
- postgresql
postgresql:
image: postgres:13
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: temporal
POSTGRES_USER: temporal
volumes:
- /var/lib/postgresql/data
temporal-ui:
image: temporalio/ui:2.13.0
ports:
- "8080:8080"
environment:
- "TEMPORAL_ADDRESS=temporal:7233"
- "TEMPORAL_CORS_ORIGINS=http://localhost:3000"
depends_on:
- temporal
import io.temporal.client.WorkflowClient;
import io.temporal.serviceclient.WorkflowServiceStubs;
public class TemporalConfig {
public WorkflowClient workflowClient() {
// Connect to local Temporal server
WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
// Configure client with namespace
return WorkflowClient.newInstance(service,
WorkflowClient.Options.newBuilder()
.setNamespace("development")
.build());
}
}
Tips
- Create separate Temporal namespaces for different developers to avoid workflow execution conflicts
- Enable debug logging in the docker-compose.yml for better visibility during development
- Mount volumes for Temporal data to persist workflow history between Docker container restarts
- Use the Temporal CLI command ‘tctl namespace register’ to create custom namespaces with specific retention policies
Adding Temporal Dependencies to Spring Boot
Setting up a Spring Boot application to work with Temporal.io requires adding specific dependencies and configuration properties to establish a reliable connection with the Temporal service.
To integrate Temporal.io with a Spring Boot application, you’ll need to add the necessary dependencies to your project’s build file. Temporal provides official Java SDK libraries that enable communication with the Temporal server and workflow execution. These dependencies include the core Temporal SDK, the Spring Boot integration, and additional components for features like workflow testing.
Once the dependencies are added, you’ll need to configure the connection to your Temporal server through application properties. This configuration includes the Temporal service endpoint, namespace, and security credentials if authentication is required. Spring Boot’s auto-configuration capabilities simplify the setup process by automatically creating and configuring the Temporal client beans based on these properties.
For production deployments, it’s important to configure appropriate client-side settings such as retry policies and timeout configurations. These settings help ensure resilience when communicating with the Temporal server and appropriate handling of transient network issues.
If your Temporal server requires authentication, you’ll need to provide the necessary credentials in your application configuration. Temporal supports various authentication methods, including mutual TLS and token-based authentication, which can be configured through Spring Boot properties.
// build.gradle
dependencies {
// Spring Boot dependencies
implementation 'org.springframework.boot:spring-boot-starter-web'
// Temporal dependencies
implementation 'io.temporal:temporal-sdk:1.20.1'
implementation 'io.temporal:temporal-spring-boot-starter:1.20.1'
// Optional: Temporal test support
testImplementation 'io.temporal:temporal-testing:1.20.1'
}
# application.properties
# Temporal connection settings
temporal.connection.target=127.0.0.1:7233
temporal.connection.namespace=default
# Optional: Authentication settings
temporal.connection.tls.enabled=true
temporal.connection.tls.key-file=/path/to/client.key
temporal.connection.tls.cert-file=/path/to/client.pem
# Client-side settings
temporal.workflow.execution-timeout=30s
temporal.workflow.retry-options.maximum-attempts=3
Tips
- Always specify the version explicitly for the Temporal dependencies to ensure compatibility.
- Use Spring profiles to manage different Temporal configurations for development, testing, and production environments.
- Store sensitive credentials like authentication tokens in secure configuration (e.g., environment variables or vault services) rather than in application.properties.
- Consider implementing health checks for your Temporal connection to monitor the service availability.
Implementing Your First Workflow
This section guides you through creating your first Temporal workflow with Spring Boot, showing how to define workflow interfaces, implement deterministic workflow logic, and integrate activities.
In Temporal, workflows are defined by interfaces that declare the entry point methods and any query or signal methods. We’ll start by creating a simple workflow interface for a payment processing system.
After defining the interface, we’ll implement the workflow logic, being careful to follow Temporal’s deterministic requirements. Workflow code must be deterministic, meaning it should produce the same results given the same inputs regardless of when or where it runs.
Activities are where you perform non-deterministic operations like API calls, database transactions, or third-party service integrations. We’ll define activity interfaces and implementations to handle the external interactions our workflow needs.
Finally, we’ll see how to start workflow executions from your Spring Boot application, handle workflow results, and properly manage exceptions that may occur during workflow execution.
// 1. Define the workflow interface
@WorkflowInterface
public interface PaymentWorkflow {
@WorkflowMethod
String processPayment(PaymentDetails paymentDetails);
@QueryMethod
String getPaymentStatus();
@SignalMethod
void cancelPayment();
}
// 2. Implement the workflow
public class PaymentWorkflowImpl implements PaymentWorkflow {
private final PaymentActivity paymentActivity =
Workflow.newActivityStub(PaymentActivity.class,
ActivityOptions.newBuilder()
.setStartToCloseTimeout(Duration.ofSeconds(10))
.build());
private String paymentStatus = "PENDING";
@Override
public String processPayment(PaymentDetails paymentDetails) {
try {
// Call activity to process payment
String confirmationId = paymentActivity.processPayment(
paymentDetails.getAmount(),
paymentDetails.getAccountId()
);
paymentStatus = "COMPLETED";
return confirmationId;
} catch (Exception e) {
paymentStatus = "FAILED";
throw Workflow.wrap(e);
}
}
@Override
public String getPaymentStatus() {
return paymentStatus;
}
@Override
public void cancelPayment() {
paymentStatus = "CANCELLED";
}
}
// 3. Define and implement the activity interface
@ActivityInterface
public interface PaymentActivity {
String processPayment(BigDecimal amount, String accountId);
}
@Component
public class PaymentActivityImpl implements PaymentActivity {
private final PaymentGatewayClient paymentGatewayClient;
public PaymentActivityImpl(PaymentGatewayClient paymentGatewayClient) {
this.paymentGatewayClient = paymentGatewayClient;
}
@Override
public String processPayment(BigDecimal amount, String accountId) {
// Call external payment gateway - non-deterministic operation
return paymentGatewayClient.processPayment(amount, accountId);
}
}
// 4. Start a workflow from Spring controller
@RestController
@RequestMapping("/payments")
public class PaymentController {
private final WorkflowClient workflowClient;
@PostMapping
public ResponseEntity<String> initiatePayment(@RequestBody PaymentRequest request) {
// Generate a unique workflow ID
String workflowId = "payment-" + UUID.randomUUID().toString();
// Start the workflow
PaymentWorkflow workflow = workflowClient.newWorkflowStub(
PaymentWorkflow.class,
WorkflowOptions.newBuilder()
.setTaskQueue("PAYMENT_TASK_QUEUE")
.setWorkflowId(workflowId)
.build());
// Start async
WorkflowClient.start(workflow::processPayment,
new PaymentDetails(request.getAmount(), request.getAccountId()));
return ResponseEntity.accepted().body(workflowId);
}
}
Tips
- Always use Workflow.currentTimeMillis() instead of System.currentTimeMillis() to ensure deterministic behavior in workflows
- Extract non-deterministic operations (like HTTP calls or database access) to activities
- Use Workflow.newThread() instead of Java’s Thread when you need parallel execution within a workflow
- For testing, Temporal provides TestWorkflowEnvironment to run workflows in a simulated environment without starting actual Temporal server
Registering Workers with Spring Components
Spring’s dependency injection capabilities can be leveraged to register and manage Temporal workers, allowing for seamless integration of workflow and activity implementations within your Spring Boot application.
To integrate Temporal workers into your Spring application, you’ll need to create a worker factory bean that can be managed by Spring’s application context. This approach allows you to leverage Spring’s dependency injection to provide workflow and activity implementations, as well as to control the lifecycle of the workers.
The worker factory is responsible for creating and configuring worker instances that connect to the Temporal service. Each worker listens to a specific task queue and executes workflows and activities registered to it. Using Spring’s @Configuration annotation, you can define beans that create and configure these workers.
Registering workflow and activity implementations is straightforward with Spring. You can inject your Spring-managed workflow and activity implementations directly into the worker registration process, ensuring that all dependencies are properly resolved.
Worker lifecycle management is another benefit of the Spring integration. By implementing Spring’s lifecycle interfaces such as InitializingBean and DisposableBean, you can ensure that workers start when your application starts and shut down gracefully when it stops.
Error handling can be centralized using Spring’s exception handling mechanisms. This allows you to implement consistent error policies across your workers and to integrate with Spring’s monitoring and metrics systems for better observability of your Temporal workflows.
@Configuration
public class TemporalWorkerConfig {
@Bean
public WorkerFactory workerFactory(WorkflowClientOptions clientOptions) {
return WorkerFactory.newInstance(
WorkflowClient.newInstance(clientOptions));
}
@Bean(initMethod = "start", destroyMethod = "shutdown")
public Worker taskQueueWorker(
WorkerFactory factory,
@Qualifier("orderWorkflow") WorkflowImpl orderWorkflowImpl,
OrderActivityImpl orderActivityImpl
) {
Worker worker = factory.newWorker("order-processing-queue");
// Register workflow implementations
worker.registerWorkflowImplementationTypes(orderWorkflowImpl.getClass());
// Register activity implementations
worker.registerActivitiesImplementations(orderActivityImpl);
// Configure worker options
WorkerOptions workerOptions = WorkerOptions.newBuilder()
.setMaxConcurrentActivityExecutionSize(100)
.setMaxConcurrentWorkflowTaskExecutionSize(100)
.build();
worker.setWorkerOptions(workerOptions);
return worker;
}
}
Tips
- Use descriptive task queue names that reflect your domain context to make debugging easier
- Consider creating separate workers for different domains to isolate failures and improve scalability
- Monitor worker metrics using Spring’s actuator to detect potential issues early
- Set appropriate concurrency limits based on your application’s resource constraints to prevent overloading
Advanced Workflow Patterns
As Temporal workflows grow in complexity, leveraging advanced patterns like child workflows, sagas, and versioning becomes essential for building resilient and maintainable microservices with Spring Boot.
Child workflows enable modular workflow design by breaking complex processes into manageable, reusable components. They can execute independently, have their own history, and allow for separation of concerns. This pattern is particularly useful when different teams own different parts of a business process or when a workflow needs to be reused across multiple parent workflows.
Saga patterns are crucial for implementing distributed transactions in microservice architectures. With Temporal, you can implement compensating transactions that reliably undo operations when a workflow fails partway through execution. This ensures data consistency across multiple services without requiring distributed locking mechanisms.
Workflow versioning is a powerful feature that allows you to update workflow implementations while maintaining compatibility with running instances. Temporal provides patching capabilities to handle workflow code changes without breaking in-flight executions, which is essential for long-running business processes that might span days or months.
Signal and query handling provide mechanisms for external communication with workflows. Signals allow external events to trigger state changes in running workflows, while queries enable inspection of workflow state without affecting execution. These features are particularly valuable for building interactive business processes that need to respond to user actions or system events.
When dealing with mutable state in workflows, it’s important to use Temporal’s side effect and mutable state constructs properly. Since workflows must be deterministic, any non-deterministic operations (like generating random numbers or getting the current time) should be wrapped in side effects to ensure consistency during workflow replays.
@WorkflowInterface
public interface OrderProcessingWorkflow {
@WorkflowMethod
void processOrder(OrderDetails orderDetails);
@SignalMethod
void cancelOrder(String reason);
@QueryMethod
OrderStatus getOrderStatus();
}
@WorkflowImpl
public class OrderProcessingWorkflowImpl implements OrderProcessingWorkflow {
private final PaymentWorkflow paymentWorkflow =
Workflow.newChildWorkflowStub(PaymentWorkflow.class);
private final ShippingWorkflow shippingWorkflow =
Workflow.newChildWorkflowStub(ShippingWorkflow.class);
private OrderStatus status = OrderStatus.CREATED;
private boolean cancelRequested = false;
private String cancellationReason;
@Override
public void processOrder(OrderDetails orderDetails) {
try {
// Start payment workflow as a child workflow
PaymentResult paymentResult = paymentWorkflow.processPayment(orderDetails.getPaymentInfo());
status = OrderStatus.PAYMENT_COMPLETED;
// Check for cancellation between steps
if (cancelRequested) {
// Compensating action for payment
paymentWorkflow.refundPayment(paymentResult.getTransactionId());
status = OrderStatus.CANCELLED;
return;
}
// Start shipping workflow as another child workflow
shippingWorkflow.arrangeShipping(orderDetails.getShippingAddress());
status = OrderStatus.SHIPPING_ARRANGED;
// Check for cancellation again
if (cancelRequested) {
// Compensating action for shipping
shippingWorkflow.cancelShipping();
// Compensating action for payment
paymentWorkflow.refundPayment(paymentResult.getTransactionId());
status = OrderStatus.CANCELLED;
return;
}
status = OrderStatus.COMPLETED;
} catch (Exception e) {
status = OrderStatus.FAILED;
throw e;
}
}
@Override
public void cancelOrder(String reason) {
this.cancelRequested = true;
this.cancellationReason = reason;
}
@Override
public OrderStatus getOrderStatus() {
return status;
}
}
Tips
- Use @WorkflowInterface and @WorkflowMethod annotations to implement child workflows with clearly defined interfaces and contract boundaries.
- Implement version patching with the Workflow.getVersion() method to handle workflow code changes without breaking in-flight executions.
- Prefer immutable workflow state where possible, and when mutable state is needed, use Workflow.mutableSideEffect() for non-deterministic operations.
- Design signal methods to be idempotent, as signals might be received multiple times due to retries or network issues.
Error Handling and Retries
Robust error handling and retry mechanisms are essential components of reliable microservices built with Temporal.io and Spring Boot, allowing systems to gracefully recover from transient failures while properly managing non-retryable errors.
Temporal.io provides sophisticated error handling capabilities that go beyond traditional exception handling patterns. When building microservices with Temporal and Spring Boot, you can configure fine-grained retry policies at both the workflow and activity levels to handle different failure scenarios.
Activity retries are particularly important for operations that might fail due to transient issues like network problems or resource unavailability. Temporal allows you to define retry policies with customizable backoff intervals, maximum attempts, and timeout configurations to ensure resilient execution even in unstable environments.
Distinguishing between retryable and non-retryable errors is crucial for system stability. While transient errors (like temporary network issues) should trigger retries, permanent errors (like validation failures) should fail fast and potentially trigger compensating actions to maintain data consistency.
For critical workflows, implementing compensating actions ensures that the system can recover gracefully when errors occur after partial execution. These actions reverse or compensate for steps that were already completed, helping maintain the system in a consistent state.
Effective monitoring of workflow failures is essential for production systems. Temporal provides visibility into workflow execution history, allowing teams to track retry attempts, identify recurring issues, and set up alerts for workflows that exceed expected execution times or failure thresholds.
public class PaymentWorkflowImpl implements PaymentWorkflow {
private final ActivityOptions paymentActivityOptions = ActivityOptions.newBuilder()
.setScheduleToCloseTimeout(Duration.ofMinutes(5))
// Configure retry options with exponential backoff
.setRetryOptions(RetryOptions.newBuilder()
.setInitialInterval(Duration.ofSeconds(1))
.setMaximumInterval(Duration.ofMinutes(1))
.setBackoffCoefficient(2.0)
.setMaximumAttempts(10)
// Define which exceptions are non-retryable
.setDoNotRetry(ValidationException.class.getName(),
FraudDetectedException.class.getName())
.build())
.build();
private final PaymentActivity paymentActivity =
Workflow.newActivityStub(PaymentActivity.class, paymentActivityOptions);
@Override
public PaymentResult processPayment(PaymentRequest request) {
try {
// Attempt to process payment
PaymentResult result = paymentActivity.processPayment(request);
return result;
} catch (ActivityFailure e) {
if (e.getCause() instanceof ValidationException) {
// Handle validation errors (non-retryable)
return new PaymentResult(Status.FAILED, "Validation error: " + e.getMessage());
} else if (e.getCause() instanceof FraudDetectedException) {
// Trigger compensating action for fraud detection
paymentActivity.notifyFraudTeam(request);
return new PaymentResult(Status.FAILED, "Potential fraud detected");
} else {
// For unexpected errors, propagate the exception
throw e;
}
}
}
}
Tips
- Configure timeout parameters carefully — set timeouts long enough to accommodate normal operation but short enough to detect actual failures
- Use custom error types to differentiate between retryable and non-retryable errors in your domain
- Implement idempotent activities to ensure that retries don’t result in duplicate operations
- Set up comprehensive logging and monitoring specifically for retry attempts to identify recurring issues
Testing Temporal Workflows in Spring Boot
Testing Temporal workflows in Spring Boot applications requires specialized approaches for both unit and integration tests, ensuring the reliability of workflow logic, activity execution, and time-dependent processes.
Unit testing Temporal workflows requires isolating the workflow logic from external dependencies. Temporal provides the TestWorkflowEnvironment class, which creates a lightweight in-memory test environment perfect for fast unit tests. This environment allows you to verify workflow execution paths without connecting to a real Temporal server.
When unit testing workflows, it’s often necessary to mock activities to focus solely on workflow logic. Temporal’s testing framework allows registering mock implementations of activities, enabling you to simulate various activity outcomes including successful responses, failures, and timeouts.
For integration testing, you’ll want to verify that your workflows interact correctly with real activity implementations and other Spring components. The Temporal test server can be started programmatically or as a test container, providing a full Temporal environment for your tests.
Spring Boot’s testing annotations integrate well with Temporal testing. You can use @SpringBootTest to load your application context and configure a test-specific Temporal client that connects to your test server. This approach ensures your workflows work correctly within your Spring application’s configuration.
Testing long-running workflows presents a particular challenge. Temporal’s test environment includes time manipulation capabilities that allow you to simulate the passage of time, making it possible to test workflows with sleep periods or timeouts without waiting for real time to pass.
public class OrderWorkflowTest {
private TestWorkflowEnvironment testEnv;
private WorkflowClient client;
private OrderActivity orderActivity;
@Before
public void setUp() {
testEnv = TestWorkflowEnvironment.newInstance();
client = testEnv.getWorkflowClient();
// Create and register activity mock
orderActivity = mock(OrderActivity.class);
testEnv.registerActivitiesImplementations(orderActivity);
}
@Test
public void testOrderWorkflowHappyPath() {
// Set up activity mock behavior
when(orderActivity.processOrder(any())).thenReturn(new OrderResult(true, "12345"));
when(orderActivity.sendNotification(anyString(), any())).thenReturn(true);
// Start workflow
OrderWorkflow workflow = client.newWorkflowStub(OrderWorkflow.class,
WorkflowOptions.newBuilder().setTaskQueue("test-queue").build());
OrderRequest request = new OrderRequest("product-123", 2);
OrderResult result = workflow.processOrder(request);
// Verify workflow result
assertTrue(result.isSuccess());
assertEquals("12345", result.getOrderId());
// Verify activity was called with correct parameters
verify(orderActivity).processOrder(request);
verify(orderActivity).sendNotification(eq("12345"), any());
}
@SpringBootTest
public class OrderWorkflowIntegrationTest {
@Autowired
private WorkflowClient workflowClient;
@Test
public void testOrderWorkflowEndToEnd() {
// Start a real workflow
OrderWorkflow workflow = workflowClient.newWorkflowStub(
OrderWorkflow.class,
WorkflowOptions.newBuilder()
.setTaskQueue("order-queue")
.setWorkflowId("test-order-" + UUID.randomUUID())
.build());
// Execute workflow and wait for result
OrderRequest request = new OrderRequest("integration-test-product", 1);
OrderResult result = workflow.processOrder(request);
// Verify the workflow completed successfully
assertTrue(result.isSuccess());
assertNotNull(result.getOrderId());
// For testing long-running workflows with time manipulation:
// TestWorkflowEnvironment testEnv = ...
// testEnv.sleep(Duration.ofDays(1)); // Fast-forward time by one day
}
Tips
- Use WorkflowReplayer for testing workflow determinism by replaying workflow history events.
- For component tests, consider using TestActivityEnvironment to test activities in isolation.
- When testing with Docker, use the Testcontainers library to manage Temporal server containers.
- Always clean up workflow executions after tests to avoid polluting your test environment.
Monitoring and Observability
Integrating Temporal.io monitoring with Spring Boot’s observability stack enables comprehensive visibility into workflow execution through metrics, tracing, and logging mechanisms.
Effective monitoring is crucial for managing microservices built with Temporal.io and Spring Boot. By leveraging Spring Boot’s robust observability features alongside Temporal’s monitoring capabilities, you can gain deep insights into workflow execution, identify bottlenecks, and detect failures before they impact users.
Micrometer integration allows you to collect and export metrics from your Temporal workflows to monitoring systems like Prometheus or Grafana. With a few configuration steps, you can track workflow execution times, failure rates, and activity completion statistics to maintain optimal performance across distributed services.
Distributed tracing with OpenTelemetry provides end-to-end visibility into workflow execution across service boundaries. By propagating trace context through Temporal workflows and activities, you can visualize the complete execution path of business processes spanning multiple services, making troubleshooting significantly easier.
Spring Boot Actuator offers a natural integration point for exposing Temporal-specific health checks and metrics endpoints. This allows operations teams to monitor workflow health using familiar tools and dashboards they already use for other Spring Boot applications.
Implementing effective alerting strategies for workflow execution issues is essential for production deployments. You can configure alerts for stuck workflows, consistently failing activities, or execution timeouts to ensure timely intervention when problems arise.
import io.micrometer.core.instrument.MeterRegistry;
import io.temporal.common.reporter.MicrometerClientStatsReporter;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
@Configuration
public class TemporalObservabilityConfig {
@Bean
public WorkflowServiceStubsOptions workflowServiceStubsOptions(MeterRegistry registry) {
return WorkflowServiceStubsOptions.newBuilder()
.setMetricsScope(new MicrometerClientStatsReporter(registry))
.build();
}
@Bean
public WorkflowClientOptions workflowClientOptions() {
return WorkflowClientOptions.newBuilder()
.setTracer(OpenTelemetrySpanFactory.newInstance())
.build();
}
}
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
@Component
public class TemporalHealthIndicator implements HealthIndicator {
private final WorkflowServiceStubs workflowServiceStubs;
public TemporalHealthIndicator(WorkflowServiceStubs workflowServiceStubs) {
this.workflowServiceStubs = workflowServiceStubs;
}
@Override
public Health health() {
try {
// Check connection to Temporal server
workflowServiceStubs.blockingStub().getSystemInfo(GetSystemInfoRequest.newBuilder().build());
return Health.up()
.withDetail("temporalNamespace", workflowServiceStubs.getOptions().getNamespace())
.build();
} catch (Exception e) {
return Health.down()
.withException(e)
.build();
}
}
}
Tips
- Use custom MDC context propagation to include workflow and run IDs in all log statements for better correlation.
- Consider implementing custom metrics for business-specific SLAs on your critical workflows.
- Set up dashboards that visualize both technical metrics and business KPIs derived from workflow execution.
- Leverage Temporal’s archival feature to retain completed workflow histories for post-execution analysis.
메타데이터
- post_id
- 1c8a570c725d
- slug
- building-reliable-microservices-with-temporal-io-and-spring-boot-1c8a570c725d
- url
- https://medium.com/@emedinam/building-reliable-microservices-with-temporal-io-and-spring-boot-1c8a570c725d
- canonical_url
- https://medium.com/@emedinam/building-reliable-microservices-with-temporal-io-and-spring-boot-1c8a570c725d
- author_url
- https://medium.com/@emedinam
- status
- ok
- fetched_at
- 2026-06-24 11:06:28