DEV Community

DevCorner2
DevCorner2

Posted on

πŸš€ Spring Boot Microservice Orchestration with Temporal

πŸ“˜ Overview

In modern distributed systems, orchestrating long-running microservice workflows is a daunting taskβ€”especially when you care about state persistence, retries, and observability. Enter Temporal.io: an open-source, developer-friendly workflow orchestration engine that offers fault-tolerant, event-driven state management out-of-the-box.

In this blog, we’ll walk through:

  • The need for orchestration in microservices
  • Why Temporal over alternatives (e.g., Camunda, Netflix Conductor)
  • Core concepts of Temporal (Workflow, Activity, Worker)
  • Setting up a Spring Boot microservice with Temporal
  • Designing and implementing an orchestration workflow
  • Error handling, retries, and versioning
  • Observability & production best practices

πŸ”§ 1. Problem Statement: Why Orchestration?

Microservices often rely on choreographed communication (event-driven), but certain use cases demand centralized coordination, like:

  • Saga pattern for distributed transactions
  • Multi-step payment or order processing
  • File processing pipelines
  • Inventory reservation and order management

Challenges with DIY orchestration:

  • Manual retry logic
  • Workflow state tracking
  • Temporal failures (e.g., server restarts)
  • Lack of visibility into flow

Temporal solves these challenges with built-in state management, retries, and history tracking.


πŸ” 2. Why Temporal for Workflow Orchestration?

Feature Traditional Orchestration Temporal
Fault-tolerant retries Manual Built-in
State persistence External DB Built-in event sourcing
Language SDKs XML / BPMN Java, Go, PHP, Python, TypeScript
Event history Custom Built-in
Dev experience Complex Developer-friendly APIs

Temporal separates orchestration (workflow) logic from business logic (activities), making it modular and testable.


🧱 3. Core Concepts of Temporal

  • Workflow: Defines the sequence of operations (orchestration logic). Deterministic and replayable.
  • Activity: Represents business logic like calling another microservice or DB.
  • Worker: A service that polls Temporal for workflow/activity tasks and executes them.
  • Temporal Server: Central component that maintains state, schedules, and history.

πŸš€ 4. Project Setup: Spring Boot + Temporal

Tech Stack:

  • Java 17
  • Spring Boot 3.x
  • Temporal SDK (Java)
  • Docker (for running Temporal server)

πŸ“ Folder Structure

springboot-temporal-orchestration/
β”‚
β”œβ”€β”€ src/main/java/com/example/temporal/
β”‚   β”œβ”€β”€ config/
β”‚   β”‚   └── TemporalConfig.java
β”‚   β”œβ”€β”€ workflow/
β”‚   β”‚   β”œβ”€β”€ OrderWorkflow.java
β”‚   β”‚   └── OrderWorkflowImpl.java
β”‚   β”œβ”€β”€ activity/
β”‚   β”‚   β”œβ”€β”€ PaymentActivity.java
β”‚   β”‚   └── PaymentActivityImpl.java
β”‚   β”œβ”€β”€ worker/
β”‚   β”‚   └── TemporalWorker.java
β”‚   └── controller/
β”‚       └── OrderController.java
β”‚
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ build.gradle / pom.xml
└── README.md
Enter fullscreen mode Exit fullscreen mode

πŸ›  5. Coding: Spring Boot + Temporal

5.1 Define Workflow Interface

@WorkflowInterface
public interface OrderWorkflow {
    @WorkflowMethod
    String processOrder(String orderId);
}
Enter fullscreen mode Exit fullscreen mode

5.2 Workflow Implementation

public class OrderWorkflowImpl implements OrderWorkflow {
    private final PaymentActivity paymentActivity = Workflow.newActivityStub(
        PaymentActivity.class,
        ActivityOptions.newBuilder()
            .setStartToCloseTimeout(Duration.ofMinutes(1))
            .build()
    );

    @Override
    public String processOrder(String orderId) {
        Workflow.getLogger(OrderWorkflowImpl.class).info("Processing order: {}", orderId);
        String result = paymentActivity.charge(orderId);
        return "Order " + orderId + " processed with status: " + result;
    }
}
Enter fullscreen mode Exit fullscreen mode

5.3 Define Activity Interface

public interface PaymentActivity {
    String charge(String orderId);
}
Enter fullscreen mode Exit fullscreen mode

5.4 Activity Implementation

public class PaymentActivityImpl implements PaymentActivity {
    @Override
    public String charge(String orderId) {
        // Simulate payment service call
        System.out.println("Charging payment for order: " + orderId);
        return "PAID";
    }
}
Enter fullscreen mode Exit fullscreen mode

βš™οΈ 6. Temporal Configuration

6.1 TemporalConfig.java

@Configuration
public class TemporalConfig {
    @Bean
    public WorkflowClient workflowClient() {
        WorkflowServiceStubs service = WorkflowServiceStubs.newInstance();
        return WorkflowClient.newInstance(service);
    }
}
Enter fullscreen mode Exit fullscreen mode

6.2 Worker Startup

@Component
public class TemporalWorker implements CommandLineRunner {
    private final WorkflowClient workflowClient;

    public TemporalWorker(WorkflowClient workflowClient) {
        this.workflowClient = workflowClient;
    }

    @Override
    public void run(String... args) {
        WorkerFactory factory = WorkerFactory.newInstance(workflowClient);
        Worker worker = factory.newWorker("ORDER_TASK_QUEUE");

        worker.registerWorkflowImplementationTypes(OrderWorkflowImpl.class);
        worker.registerActivitiesImplementations(new PaymentActivityImpl());

        factory.start();
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“‘ 7. Triggering the Workflow

OrderController.java

@RestController
@RequestMapping("/orders")
public class OrderController {
    private final WorkflowClient client;

    public OrderController(WorkflowClient client) {
        this.client = client;
    }

    @PostMapping("/{id}")
    public ResponseEntity<String> startOrderWorkflow(@PathVariable String id) {
        OrderWorkflow workflow = client.newWorkflowStub(
            OrderWorkflow.class,
            WorkflowOptions.newBuilder()
                .setTaskQueue("ORDER_TASK_QUEUE")
                .build()
        );

        WorkflowClient.start(workflow::processOrder, id);
        return ResponseEntity.ok("Order processing started for ID: " + id);
    }
}
Enter fullscreen mode Exit fullscreen mode

⚠️ 8. Error Handling & Retries

Temporal handles retries, timeouts, and failures automatically via options:

ActivityOptions options = ActivityOptions.newBuilder()
    .setStartToCloseTimeout(Duration.ofSeconds(30))
    .setRetryOptions(RetryOptions.newBuilder()
        .setMaximumAttempts(3)
        .build())
    .build();
Enter fullscreen mode Exit fullscreen mode

You can also:

  • Catch exceptions inside workflows
  • Define custom backoff
  • Implement compensation workflows (Saga)

πŸ“Š 9. Observability with Temporal UI

Run docker-compose.yml with Temporal UI:

temporal:
  image: temporalio/auto-setup
  ports:
    - "7233:7233"
    - "8088:8088" # Temporal Web UI
Enter fullscreen mode Exit fullscreen mode

Visit http://localhost:8088 to view:

  • Workflow status
  • Event history
  • Retry attempts
  • Logs

🏁 10. Best Practices for Production

Concern Best Practice
Determinism Avoid non-deterministic code in workflows (e.g., random, time)
Workflow versioning Use Workflow.getVersion() when updating logic
Timeouts Set proper timeouts per activity
Idempotency Activities should be idempotent for retries
Separation of concerns Keep workflows orchestration-only, move logic to activities
Scalability Run multiple workers, use task queues

πŸ“¦ 11. Use Cases for Temporal

  • πŸ›’ E-commerce order fulfillment
  • πŸ’³ Payment orchestration
  • πŸ“ File processing pipelines
  • 🧾 Billing & invoice generation
  • πŸ” Retryable integrations with third-party services

πŸ“š 12. References & Resources


βœ… Conclusion

Temporal enables resilient, scalable, and maintainable orchestration of distributed workflowsβ€”exactly what modern microservices need. By pairing Temporal with Spring Boot, you empower developers to write workflows like regular code, eliminating the boilerplate of error handling, retries, and state management.

πŸ’‘ Build orchestration like a pro, not like a plumber. Use Temporal.

Top comments (0)