Files
2026-06-02 20:48:51 +00:00

12 KiB

Temporal Spring Boot Integration

Overview

io.temporal:temporal-spring-boot-starter auto-configures Workers, registers Workflow / Activity / Nexus Service implementations, and exposes WorkflowClient as a Spring bean. This eliminates the manual WorkflowServiceStubs to WorkflowClient to WorkerFactory setup required without Spring.

Temporal's Spring Boot integration supports Spring Boot 2.x, 3.x, and 4.x .

Dependency Setup

Maven :

<dependency>
    <groupId>io.temporal</groupId>
    <artifactId>temporal-spring-boot-starter</artifactId>
    <version>1.31.0</version>
</dependency>

Gradle :

implementation 'io.temporal:temporal-spring-boot-starter:1.31.0'

The starter transitively includes temporal-sdk and the autoconfigure module.

Minimal Configuration

The minimum to autowire a WorkflowClient is a connection.target :

spring.temporal:
  connection:
    target: local # shorthand for localhost:7233; use host:port for remote

To set a non-default Namespace, add spring.temporal.namespace :

spring.temporal:
  connection:
    target: local
  namespace: my-namespace

Temporal Cloud with API key

spring.temporal:
  connection:
    target: <region>.<account>.tmprl.cloud:7233
    apiKey: <API key>
  namespace: <namespace>

Temporal Cloud with mTLS

spring.temporal:
  connection:
    mtls:
      target: <region>.<account>.tmprl.cloud:7233
      key-file: /path/to/key.key
      cert-chain-file: /path/to/cert.pem
  namespace: <namespace>

cert-chain-file may be omitted when the key-file is a PKCS12 bundle that already contains the certificate chain .

Interface Design and Spring Annotation Layering

Temporal SDK annotations go on interfaces, Spring Boot autoconfigure annotations go on implementation classes. The interface side is identical to non-Spring usage.

Workflow interface

package greetingapp;

import io.temporal.workflow.WorkflowInterface;
import io.temporal.workflow.WorkflowMethod;

@WorkflowInterface
public interface GreetingWorkflow {
    @WorkflowMethod
    String greet(String name);
}

Workflow implementation

io.temporal.spring.boot.WorkflowImpl replaces the manual worker.registerWorkflowImplementationTypes() call. The workers member names the Worker(s) this class registers with.

package greetingapp;

import io.temporal.activity.ActivityOptions;
import io.temporal.spring.boot.WorkflowImpl;
import io.temporal.workflow.Workflow;

import java.time.Duration;

// No @Component — Temporal creates a new instance per execution.
@WorkflowImpl(workers = "greeting-worker")
public class GreetingWorkflowImpl implements GreetingWorkflow {

    private final GreetActivities activities = Workflow.newActivityStub(
        GreetActivities.class,
        ActivityOptions.newBuilder()
            .setStartToCloseTimeout(Duration.ofSeconds(30))
            .setTaskQueue("greeting-queue")
            .build()
    );

    @Override
    public String greet(String name) {
        return activities.greet(name);
    }
}

Activity interface

package greetingapp;

import io.temporal.activity.ActivityInterface;
import io.temporal.activity.ActivityMethod;

@ActivityInterface
public interface GreetActivities {
    @ActivityMethod
    String greet(String name);
}

Activity implementation

io.temporal.spring.boot.ActivityImpl must be applied to a Spring bean; the documented way is to add @Component .

package greetingapp;

import io.temporal.spring.boot.ActivityImpl;
import org.springframework.stereotype.Component;

@Component
@ActivityImpl(workers = "greeting-worker") // docs/develop/java/integrations/spring-boot.mdx:157
public class GreetActivitiesImpl implements GreetActivities {

    private final GreetingService greetingService;

    public GreetActivitiesImpl(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @Override
    public String greet(String name) {
        return greetingService.composeGreeting(name);
    }
}

Nexus Service implementations follow the same pattern using io.temporal.spring.boot.NexusServiceImpl ; the impl must be a Spring bean.

Configure Workers

The integration supports two configuration methods for Workers: explicit configuration and auto-discovery . They compose: auto-discovery is applied after and on top of explicit configuration .

Explicit configuration

The workers: block lists Workers and their members :

spring.temporal:
  workers:
    - task-queue: greeting-queue
      name: greeting-worker # if omitted, the Task Queue name is used as the Worker name
      workflow-classes:
        - greetingapp.GreetingWorkflowImpl
      activity-beans:
        - greetActivitiesImpl
  • task-queue is the Task Queue the Worker polls.
  • name is the unique Worker name; it defaults to the Task Queue when omitted .
  • workflow-classes lists fully qualified Workflow implementation class names .
  • activity-beans lists Spring bean names of Activity implementations .

Auto-discovery

Auto-discovery lets you skip listing Workflow classes, Activity beans, and Nexus Service beans by referencing Worker Task Queue names or Worker names on the implementations themselves .

spring.temporal:
  workers-auto-discovery:
    packages:
      - greetingapp # docs/develop/java/integrations/spring-boot.mdx:138-142

Auto-discovery scope

Auto-discovery picks up exactly the following :

  • Workflow implementation classes annotated with io.temporal.spring.boot.WorkflowImpl .
  • Activity beans present in the Spring context whose implementations are annotated with io.temporal.spring.boot.ActivityImpl .
  • Nexus Service beans present in the Spring context whose implementations are annotated with io.temporal.spring.boot.NexusServiceImpl .
  • Workers themselves — if a Task Queue or Worker name is referenced by one of the annotations but not explicitly configured, a Worker is created with default options .

Auto-discovered Workflow classes, Activity beans, and Nexus Service beans are registered with configured Workers if not already registered .

ActivityImpl and NexusServiceImpl only work when the implementation is a Spring bean (for example, annotated with @Component) .

Explicit vs auto-discovery, side by side

Auto-discovery via annotations:

spring.temporal:
  workers-auto-discovery:
    packages:
      - greetingapp
@Component
@ActivityImpl(workers = "greeting-worker")
public class GreetActivitiesImpl implements GreetActivities { }

Explicit registration:

spring.temporal:
  workers:
    - task-queue: greeting-queue
      name: greeting-worker
      workflow-classes:
        - greetingapp.GreetingWorkflowImpl
      activity-beans:
        - greetActivitiesImpl

Use auto-discovery when implementations are colocated in a package tree (most apps). Use explicit configuration when registering beans defined outside scanned packages, or when you want the YAML to be the single source of truth.

WorkflowClient Injection

WorkflowClient is autowired by the integration . Inject it into any @Service or @RestController:

@Service
public class GreetingStarter {

    private final WorkflowClient client;

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

    public String startGreeting(String name) {
        var stub = client.newWorkflowStub(
            GreetingWorkflow.class,
            WorkflowOptions.newBuilder()
                .setWorkflowId(UUID.randomUUID().toString())
                .setTaskQueue("greeting-queue") // must match the Worker's Task Queue
                .build()
        );
        return stub.greet(name);
    }
}

Interceptors

Create beans implementing one of io.temporal.common.interceptors.WorkflowClientInterceptor, io.temporal.common.interceptors.ScheduleClientInterceptor, or io.temporal.common.interceptors.WorkerInterceptor . Registration order is controlled by Spring's org.springframework.core.annotation.Order annotation on each bean — lower values run first.

import io.temporal.common.interceptors.WorkerInterceptor;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(10)
public class TracingWorkerInterceptor implements WorkerInterceptor {
    // ...
}

These three interceptor interfaces are the only ordering hook the integration documents — there is no Temporal-specific ordering API. For broader extension (registering Workflow / Activity / Nexus types, modifying Worker or Client options across the SDK), see the Temporal Plugin system.

Customization of Options

Create a bean implementing io.temporal.spring.boot.TemporalOptionsCustomizer<OptionsBuilderType> to programmatically adjust options after the YAML/property values are applied. The supported builder types are :

  • WorkflowServiceStubsOptions.Builder
  • WorkflowClientOptions.Builder
  • WorkerFactoryOptions.Builder
  • WorkerOptions.Builder
  • WorkflowImplementationOptions.Builder
  • TestEnvironmentOptions.Builder

For per-Task-Queue or per-Worker-name customization of WorkerOptions, use io.temporal.spring.boot.WorkerOptionsCustomizer instead of the generic form . For per-Workflow-Type customization of WorkflowImplementationOptions, use io.temporal.spring.boot.WorkflowImplementationOptionsCustomizer .

Integrations (metrics and tracing)

The integration picks up a io.micrometer.core.instrument.MeterRegistry bean (for example, from Spring Boot Actuator) and reports Temporal metrics through it .

For tracing, the integration picks up the OpenTelemetry bean configured by spring-cloud-sleuth-otel-autoconfigure, or a custom io.opentelemetry.api.OpenTelemetry / io.opentracing.Tracer bean in the application context .

The Spring AI integration (io.temporal:temporal-spring-ai) builds on this Spring Boot integration: when on the classpath, its SpringAiPlugin auto-registers ChatModelActivity with all Temporal Workers created by the Spring Boot integration .

Testing

Switch the client to an in-memory io.temporal.testing.TestWorkflowEnvironment by enabling the test server :

spring.temporal:
  test-server:
    enabled: true

When spring.temporal.test-server.enabled: true is set, the spring.temporal.connection block is ignored . You can then autowire the TestWorkflowEnvironment alongside WorkflowClient:

@SpringBootTest(classes = Test.Configuration.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class Test {
    @Autowired ConfigurableApplicationContext applicationContext;
    @Autowired TestWorkflowEnvironment testWorkflowEnvironment;
    @Autowired WorkflowClient workflowClient;

    @BeforeEach
    void setUp() {
        applicationContext.start();
    }

    @ComponentScan // discovers @Component-annotated Activity beans
    public static class Configuration {}
}

For unit tests without Spring, use TestWorkflowExtension or TestWorkflowEnvironment directly; see Java SDK test frameworks. Don't mix Spring integration tests and direct TestWorkflowExtension tests in the same test class — pick one.

Spring-Specific Gotchas

Workflow impls must not have @Component. Temporal creates a new Workflow instance per execution. Adding @Component makes Spring also manage it as a singleton bean, causing confusing lifecycle behavior. Leave @WorkflowImpl classes as plain classes with no Spring stereotype.

Activity beans are Spring singletons. Temporal may invoke Activity methods concurrently across many Workflow executions. Don't keep mutable instance state on Activity beans — use injected stateless or thread-safe services instead.

@WorkflowImpl / @ActivityImpl without workers-auto-discovery.packages is silently ignored. Without the packages list, nothing scans the annotations and nothing registers. Verify in the Temporal UI that the Worker reports the expected Workflow and Activity types.

ActivityOptions.setTaskQueue(...) is still required on Activity stubs. @ActivityImpl(workers = "...") only binds the Activity bean to a Worker; it doesn't route Activity Task execution. Inside Workflow code, set .setTaskQueue(...) on ActivityOptions to direct Activity Tasks to the right queue.

Multiple DataConverter beans cause ambiguity. If you declare more than one DataConverter bean, designate the primary by naming one of them mainDataConverter.