From 40a5146be7b5f5bf4ba7f66047df8e2912a1b006 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 20 Apr 2026 13:03:51 -0400 Subject: [PATCH 01/15] Add Spring AI integration documentation for Java SDK Documents the new temporal-spring-ai module that makes Spring AI model calls, tools, vector stores, embeddings, and MCP calls durable Temporal primitives. --- docs/develop/integrations.mdx | 1 + docs/develop/java/integrations/index.mdx | 15 +- docs/develop/java/integrations/spring-ai.mdx | 162 +++++++++++++++++++ sidebars.js | 1 + 4 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 docs/develop/java/integrations/spring-ai.mdx diff --git a/docs/develop/integrations.mdx b/docs/develop/integrations.mdx index 92f5afa45e..ad7982bc8a 100644 --- a/docs/develop/integrations.mdx +++ b/docs/develop/integrations.mdx @@ -17,5 +17,6 @@ description: AI framework integrations available for Temporal SDKs. Temporal integrates with popular AI and agent frameworks. The following integrations are available by SDK: +- [Java SDK integrations](/develop/java/integrations) - [Python SDK integrations](/develop/python/integrations) - [TypeScript SDK integrations](/develop/typescript/integrations) diff --git a/docs/develop/java/integrations/index.mdx b/docs/develop/java/integrations/index.mdx index d2713d4406..5103398ef8 100644 --- a/docs/develop/java/integrations/index.mdx +++ b/docs/develop/java/integrations/index.mdx @@ -6,15 +6,28 @@ description: This section covers integrations with the Java SDK toc_max_heading_level: 4 keywords: - Java SDK + - integrations + - ai frameworks tags: - Java SDK - Temporal SDKs + - Integrations --- import * as Components from '@site/src/components'; ![Java SDK Banner](/img/assets/banner-java-temporal.png) -## Integrations +## Framework integrations - [Spring Boot](/develop/java/integrations/spring-boot-integration) + +## AI integrations + +The following AI framework integrations are available for the Temporal Java SDK: + +| Framework | SDK docs | Integration guide | +| --- | --- | --- | +| Spring AI | [docs.spring.io/spring-ai](https://docs.spring.io/spring-ai/reference/) | [Guide](/develop/java/integrations/spring-ai) | + +These integrations are built on the Temporal Java SDK's [Plugin system](/develop/plugins-guide), which you can also use to build your own integrations. diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx new file mode 100644 index 0000000000..5242688d9f --- /dev/null +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -0,0 +1,162 @@ +--- +id: spring-ai +title: Spring AI integration - Java SDK +sidebar_label: Spring AI +slug: /develop/java/integrations/spring-ai +toc_max_heading_level: 2 +keywords: + - ai + - agents + - spring ai + - java + - integrations +tags: + - Spring AI + - Java SDK + - Temporal SDKs + - Integrations + - AI Frameworks +description: Build durable AI Workflows in Java with the Temporal Spring AI integration. +--- + +The [Temporal Spring AI integration](https://central.sonatype.com/artifact/io.temporal/temporal-spring-ai) lets you call [Spring AI](https://docs.spring.io/spring-ai/reference/) models, tools, vector stores, embeddings, and MCP servers from Temporal Workflows as durable Activities. Model calls and tool invocations are recorded in Workflow history, so they retry on failure and replay deterministically — without you having to change how you write Spring AI code. + +The integration is built on the Temporal Java SDK's [Plugin system](/develop/plugins-guide) and is distributed as the `io.temporal:temporal-spring-ai` module alongside the existing [Spring Boot integration](/develop/java/integrations/spring-boot-integration). + +## Compatibility + +| Dependency | Minimum version | +| ----------------- | --------------- | +| Java | 17 | +| Spring Boot | 3.x | +| Spring AI | 1.1.0 | +| Temporal Java SDK | 1.33.0 | + +## Add the dependency + +Add `temporal-spring-ai` alongside `temporal-spring-boot-starter` and a Spring AI model starter (for example, `spring-ai-starter-model-openai`). + +**[Apache Maven](https://maven.apache.org/):** + +```xml + + io.temporal + temporal-spring-ai + ${temporal-sdk.version} + +``` + +**[Gradle Groovy DSL](https://gradle.org/):** + +```groovy +implementation "io.temporal:temporal-spring-ai:${temporalSdkVersion}" +``` + +When `temporal-spring-ai` is on the classpath, the `SpringAiPlugin` auto-registers `ChatModelActivity` with all Temporal Workers created by the Spring Boot integration. Optional Activities are auto-configured when their dependencies are present: + +| Feature | Dependency | Registered Activity | +| ------------ | --------------- | ------------------------ | +| Vector store | `spring-ai-rag` | `VectorStoreActivity` | +| Embeddings | `spring-ai-rag` | `EmbeddingModelActivity` | +| MCP | `spring-ai-mcp` | `McpClientActivity` | + +## Call a chat model from a Workflow + +Use `ActivityChatModel` as a Spring AI `ChatModel` inside a Workflow. Every call goes through a Temporal Activity, so model responses are durable and retried per your Activity options. + +Wrap `ActivityChatModel` in a `TemporalChatClient` to build prompts and register tools: + +```java +@WorkflowInit +public MyWorkflowImpl(String goal) { + ActivityChatModel chatModel = ActivityChatModel.forDefault(); + + WeatherActivity weather = Workflow.newActivityStub( + WeatherActivity.class, activityOptions); + + this.chatClient = TemporalChatClient.builder(chatModel) + .defaultSystem("You are a helpful assistant.") + .defaultTools(weather, new TimestampTools()) + .build(); +} + +@Override +public String run(String goal) { + return chatClient.prompt().user(goal).call().content(); +} +``` + +`ActivityChatModel.forDefault()` resolves to the default Spring AI `ChatModel` bean. To target a specific model in a multi-model application, pass its bean name to `ActivityChatModel.forModel("openai")`. + +:::note +Streaming responses are not supported. `ActivityChatModel.stream()` throws `UnsupportedOperationException` because streaming through Temporal Activities isn't a durable operation. +::: + +## Register tools + +Tools passed to `defaultTools()` are dispatched based on their type. The integration handles Temporal determinism for you when the tool is durable, and gives you control when it isn't. + +### Activity stubs + +An interface annotated with both `@ActivityInterface` and Spring AI `@Tool` methods is auto-detected and executed as a Temporal Activity. Use this for external calls that need retries and timeouts. + +```java +@ActivityInterface +public interface WeatherActivity { + @Tool(description = "Get weather for a city") + @ActivityMethod + String getWeather(String city); +} +``` + +### Nexus service stubs + +Nexus service stubs with `@Tool` methods are auto-detected and invoked as [Nexus operations](/develop/java/nexus), enabling cross-Namespace tool calls. + +### `@SideEffectTool` + +Classes annotated with `@SideEffectTool` have each `@Tool` method wrapped in `Workflow.sideEffect()`. The result is recorded in history on first execution and replayed from history afterward. Use this for cheap, non-deterministic operations such as timestamps or UUIDs. + +```java +@SideEffectTool +public class TimestampTools { + @Tool(description = "Get current time") + public String now() { + return Instant.now().toString(); + } +} +``` + +### Plain tools + +Any class with `@Tool` methods that isn't an Activity stub, Nexus stub, or `@SideEffectTool` runs directly on the Workflow thread. You're responsible for determinism — call Activities, `Workflow.sideEffect()`, child Workflows, or other durable primitives as needed. + +```java +public class MyTools { + @Tool(description = "Process data") + public String process(String input) { + SomeActivity act = Workflow.newActivityStub(SomeActivity.class, opts); + return act.doWork(input); + } +} +``` + +## Use vector stores, embeddings, and MCP + +When the corresponding Spring AI modules are on the classpath, the integration registers Activities for vector stores, embeddings, and MCP tool calls. Inject the matching Spring AI types into your Activities or Workflows and use them as you would in any Spring AI application — each operation is executed through a Temporal Activity. + +You can also register these plugins explicitly, without relying on auto-configuration: + +```java +new VectorStorePlugin(vectorStore); +new EmbeddingModelPlugin(embeddingModel); +new McpPlugin(); +``` + +`ActivityMcpClient` wraps a Spring AI MCP client so that remote MCP tool calls become durable Activity executions. + +## Learn more + +- [`temporal-spring-ai` README](https://github.com/temporalio/sdk-java/blob/master/temporal-spring-ai/README.md) — full reference for the module +- [Spring Boot integration](/develop/java/integrations/spring-boot-integration) — required companion module +- [Plugin system](/develop/plugins-guide) — how integrations are registered with Workers and Clients diff --git a/sidebars.js b/sidebars.js index 7bfedd9f2e..5b6393680f 100644 --- a/sidebars.js +++ b/sidebars.js @@ -345,6 +345,7 @@ module.exports = { }, items: [ 'develop/java/integrations/spring-boot', + 'develop/java/integrations/spring-ai', ], }, ], From 2dc8343455d4fb06ad4b069195c07a7b9709e2dd Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 20 Apr 2026 13:59:30 -0400 Subject: [PATCH 02/15] Update Temporal Java SDK version to 1.35.0 --- docs/develop/java/integrations/spring-ai.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index 5242688d9f..737825c16b 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -30,7 +30,7 @@ The integration is built on the Temporal Java SDK's [Plugin system](/develop/plu | Java | 17 | | Spring Boot | 3.x | | Spring AI | 1.1.0 | -| Temporal Java SDK | 1.33.0 | +| Temporal Java SDK | 1.35.0 | ## Add the dependency From b0a63980c132686f90ff6d2f5fcc9b7eb74e6be2 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 20 Apr 2026 16:30:18 -0400 Subject: [PATCH 03/15] clarify public preview --- docs/develop/java/integrations/spring-ai.mdx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index 737825c16b..e051d0b914 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -23,6 +23,13 @@ The [Temporal Spring AI integration](https://central.sonatype.com/artifact/io.te The integration is built on the Temporal Java SDK's [Plugin system](/develop/plugins-guide) and is distributed as the `io.temporal:temporal-spring-ai` module alongside the existing [Spring Boot integration](/develop/java/integrations/spring-boot-integration). +:::info + +The Spring AI Integration is in Public Preview. Refer to the +[Temporal product release stages guide](/evaluate/development-production-features/release-stages) for more information. + +::: + ## Compatibility | Dependency | Minimum version | From 49df59c91703deda064be611a72cbaa7f4175116 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 21 Apr 2026 10:22:04 -0400 Subject: [PATCH 04/15] Edit streaming --- docs/develop/java/integrations/spring-ai.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index e051d0b914..b264baf56d 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -96,7 +96,7 @@ public String run(String goal) { `ActivityChatModel.forDefault()` resolves to the default Spring AI `ChatModel` bean. To target a specific model in a multi-model application, pass its bean name to `ActivityChatModel.forModel("openai")`. :::note -Streaming responses are not supported. `ActivityChatModel.stream()` throws `UnsupportedOperationException` because streaming through Temporal Activities isn't a durable operation. +Streaming responses are not currently supported. ::: ## Register tools From 8e5f4ea9f160486582e61db906c3b2ee53f185dc Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 21 Apr 2026 10:27:10 -0400 Subject: [PATCH 05/15] edit plain tools --- docs/develop/java/integrations/spring-ai.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index b264baf56d..0fc7728d8c 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -136,7 +136,7 @@ public class TimestampTools { ### Plain tools -Any class with `@Tool` methods that isn't an Activity stub, Nexus stub, or `@SideEffectTool` runs directly on the Workflow thread. You're responsible for determinism — call Activities, `Workflow.sideEffect()`, child Workflows, or other durable primitives as needed. +Any class with `@Tool` methods that isn't an Activity stub, Nexus stub, or `@SideEffectTool` runs directly on the Workflow thread. Use this for inherently deterministic tools (such as updating in-memory agent state), or for orchestration of durable primitives as you need, e.g. calling multiple Activities, child Workflows, wait conditions, or other Temporal durable primitives. ```java public class MyTools { From b9edcf290b61f9b4e55e727c047b4b89e17a0fc0 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 27 Apr 2026 17:02:58 -0400 Subject: [PATCH 06/15] spring-ai docs: cover activity options, provider passthrough, media cap Bump the documented Java SDK minimum to 1.35.0 (the version where the spring-ai work landed) and add three sections that the original draft predated: - Activity options and retry behavior (#2853, #2855): defaults, the non-retryable-AI-error classification, ad-hoc forDefault(ActivityOptions), the ChatModelActivityOptions bean for configuration-driven per-model overrides, and the "default" catch-all key for compositional setups. - Provider-specific chat options (#2857): AnthropicChatOptions extended- thinking example demonstrating that subclass-specific fields survive the Activity boundary. - Media in messages (#2860): 1 MiB inline-bytes cap, URI-based alternative, io.temporal.springai.maxMediaBytes override. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/develop/java/integrations/spring-ai.mdx | 63 ++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index 0fc7728d8c..12adf2f1ee 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -99,6 +99,69 @@ public String run(String goal) { Streaming responses are not currently supported. ::: +## Activity options and retry behavior + +`ActivityChatModel.forDefault()` and `forModel(name)` build the chat Activity stub with sensible defaults: a 2-minute start-to-close timeout, 3 attempts, and `org.springframework.ai.retry.NonTransientAiException` and `java.lang.IllegalArgumentException` classified as non-retryable so a bad API key or invalid prompt fails fast. + +Pass an `ActivityOptions` directly when you need finer control — a specific Task Queue, heartbeats, priority, or a custom `RetryOptions`: + +```java +ActivityChatModel chatModel = ActivityChatModel.forDefault( + ActivityOptions.newBuilder(ActivityChatModel.defaultActivityOptions()) + .setTaskQueue("chat-heavy") + .build()); +``` + +For configuration-driven per-model overrides, declare a `ChatModelActivityOptions` bean. The plugin consults it whenever `forDefault()` or `forModel(name)` runs in a Workflow. Use the special key `ChatModelTypes.DEFAULT_MODEL_NAME` (the literal `"default"`) as a global catch-all that applies to any model not explicitly listed — including models contributed by third-party starters: + +```java +@Bean +ChatModelActivityOptions chatModelActivityOptions() { + ActivityOptions fiveMinute = + ActivityOptions.newBuilder(ActivityChatModel.defaultActivityOptions()) + .setStartToCloseTimeout(Duration.ofMinutes(5)) + .build(); + return new ChatModelActivityOptions(Map.of( + ChatModelTypes.DEFAULT_MODEL_NAME, fiveMinute, // global baseline + "claude", ActivityOptions.newBuilder(fiveMinute) // overrides for "claude" + .setTaskQueue("claude-heavy") + .build())); +} +``` + +Keys that neither match a registered `ChatModel` bean nor equal `"default"` cause plugin construction to fail, so a typo surfaces at startup rather than at first call. + +`ActivityMcpClient.create()` and `create(ActivityOptions)` work the same way for MCP tool calls, with a 30-second default timeout. + +## Provider-specific chat options + +Provider-specific `ChatOptions` subclasses — for example, `AnthropicChatOptions` to enable extended thinking, or `OpenAiChatOptions` to set `reasoning_effort` — pass through the Activity boundary unchanged. Attach them via `ChatClient.defaultOptions(...)` and the plugin re-applies them on the Activity side before calling the underlying model: + +```java +AnthropicChatOptions thinking = AnthropicChatOptions.builder() + .thinking(AnthropicApi.ThinkingType.ENABLED, 1024) + .temperature(1.0) + .maxTokens(4096) + .build(); + +ChatClient client = TemporalChatClient.builder(ActivityChatModel.forModel("anthropic")) + .defaultOptions(thinking) + .build(); +``` + +The pass-through relies on the `ChatOptions` subclass overriding `copy()` to return its own type — every provider class shipped with Spring AI does. + +## Media in messages + +Prefer URI-based media when attaching images, audio, or other binary content to chat messages. Raw `byte[]` media gets serialized into every chat Activity's input and result payload, which end up inside Temporal Workflow history events. Server-side history events have a fixed 2 MiB size limit; to leave headroom for messages, tool definitions, and options, the plugin enforces a **1 MiB default cap** on inline bytes and fails fast with a non-retryable `ApplicationFailure` pointing at the URI alternative. + +```java +// Preferred — only the URL crosses the Activity boundary. +Media image = new Media(MimeTypeUtils.IMAGE_PNG, URI.create("https://cdn.example.com/pic.png")); +``` + +Override the cap by setting the system property `io.temporal.springai.maxMediaBytes` before your worker starts (positive integer; `0` disables the check). For anything larger than a small thumbnail, route the bytes to a binary store from an Activity and pass only the URL across the conversation. + ## Register tools Tools passed to `defaultTools()` are dispatched based on their type. The integration handles Temporal determinism for you when the tool is durable, and gives you control when it isn't. From 45fe40062727a22c0e7bcc3415982ad9316f4e02 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 27 Apr 2026 17:23:56 -0400 Subject: [PATCH 07/15] spring-ai docs: lead with agents, honor workflow-side tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reframe the opener around Spring AI as an agent framework and the integration as making agents durable, instead of describing it mechanically as "wraps calls as Activities." Avoids overstating that "every tool" goes through an Activity — the integration explicitly supports Workflow-side tools (@SideEffectTool, plain @Tool) alongside Activity and Nexus stubs. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/develop/java/integrations/spring-ai.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index 12adf2f1ee..5c4b6515d3 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -16,10 +16,10 @@ tags: - Temporal SDKs - Integrations - AI Frameworks -description: Build durable AI Workflows in Java with the Temporal Spring AI integration. +description: Build durable AI agents in Java with the Temporal Spring AI integration. --- -The [Temporal Spring AI integration](https://central.sonatype.com/artifact/io.temporal/temporal-spring-ai) lets you call [Spring AI](https://docs.spring.io/spring-ai/reference/) models, tools, vector stores, embeddings, and MCP servers from Temporal Workflows as durable Activities. Model calls and tool invocations are recorded in Workflow history, so they retry on failure and replay deterministically — without you having to change how you write Spring AI code. +[Spring AI](https://docs.spring.io/spring-ai/reference/) is an agent framework for Java applications — chat clients, tool calling, vector stores, embeddings, and MCP servers, all wired through Spring Boot. The [Temporal Spring AI integration](https://central.sonatype.com/artifact/io.temporal/temporal-spring-ai) makes Spring AI agents durable: model calls run through Temporal Activities recorded in Workflow history, and tools are dispatched per their type so each kind lands in the right place in Workflow execution — Activity stubs and Nexus stubs as durable operations, `@SideEffectTool` classes wrapped in `Workflow.sideEffect`, and plain tools running directly in Workflow code. Agents retry on failure and replay deterministically without changing how you write Spring AI code. The integration is built on the Temporal Java SDK's [Plugin system](/develop/plugins-guide) and is distributed as the `io.temporal:temporal-spring-ai` module alongside the existing [Spring Boot integration](/develop/java/integrations/spring-boot-integration). From 658eb2fdacf2bc5b72b2002b182cb394e8c6d7de Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 28 Apr 2026 10:34:34 -0400 Subject: [PATCH 08/15] spring-ai docs: replace inline code with snipsync placeholders Switches six extended code samples on the spring-ai integration page from inline copies to snipsync references against samples-java markers added in temporalio/samples-java#fbae80a. Snippets pulled: - samples-java-spring-ai-chat-workflow-init - samples-java-spring-ai-activity-tool - samples-java-spring-ai-side-effect-tool - samples-java-spring-ai-plain-tool - samples-java-spring-ai-per-model-options - samples-java-spring-ai-provider-options Short single-purpose snippets (Maven dep, one-liner forDefault override, Media URI line, plugin triplet) stay inline. The daily snipsync workflow will fill the placeholders once the samples-java branch lands on master. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/develop/java/integrations/spring-ai.mdx | 82 +++----------------- 1 file changed, 12 insertions(+), 70 deletions(-) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index 5c4b6515d3..8ee7e4316f 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -73,25 +73,8 @@ Use `ActivityChatModel` as a Spring AI `ChatModel` inside a Workflow. Every call Wrap `ActivityChatModel` in a `TemporalChatClient` to build prompts and register tools: -```java -@WorkflowInit -public MyWorkflowImpl(String goal) { - ActivityChatModel chatModel = ActivityChatModel.forDefault(); - - WeatherActivity weather = Workflow.newActivityStub( - WeatherActivity.class, activityOptions); - - this.chatClient = TemporalChatClient.builder(chatModel) - .defaultSystem("You are a helpful assistant.") - .defaultTools(weather, new TimestampTools()) - .build(); -} - -@Override -public String run(String goal) { - return chatClient.prompt().user(goal).call().content(); -} -``` + + `ActivityChatModel.forDefault()` resolves to the default Spring AI `ChatModel` bean. To target a specific model in a multi-model application, pass its bean name to `ActivityChatModel.forModel("openai")`. @@ -114,20 +97,8 @@ ActivityChatModel chatModel = ActivityChatModel.forDefault( For configuration-driven per-model overrides, declare a `ChatModelActivityOptions` bean. The plugin consults it whenever `forDefault()` or `forModel(name)` runs in a Workflow. Use the special key `ChatModelTypes.DEFAULT_MODEL_NAME` (the literal `"default"`) as a global catch-all that applies to any model not explicitly listed — including models contributed by third-party starters: -```java -@Bean -ChatModelActivityOptions chatModelActivityOptions() { - ActivityOptions fiveMinute = - ActivityOptions.newBuilder(ActivityChatModel.defaultActivityOptions()) - .setStartToCloseTimeout(Duration.ofMinutes(5)) - .build(); - return new ChatModelActivityOptions(Map.of( - ChatModelTypes.DEFAULT_MODEL_NAME, fiveMinute, // global baseline - "claude", ActivityOptions.newBuilder(fiveMinute) // overrides for "claude" - .setTaskQueue("claude-heavy") - .build())); -} -``` + + Keys that neither match a registered `ChatModel` bean nor equal `"default"` cause plugin construction to fail, so a typo surfaces at startup rather than at first call. @@ -137,17 +108,8 @@ Keys that neither match a registered `ChatModel` bean nor equal `"default"` caus Provider-specific `ChatOptions` subclasses — for example, `AnthropicChatOptions` to enable extended thinking, or `OpenAiChatOptions` to set `reasoning_effort` — pass through the Activity boundary unchanged. Attach them via `ChatClient.defaultOptions(...)` and the plugin re-applies them on the Activity side before calling the underlying model: -```java -AnthropicChatOptions thinking = AnthropicChatOptions.builder() - .thinking(AnthropicApi.ThinkingType.ENABLED, 1024) - .temperature(1.0) - .maxTokens(4096) - .build(); - -ChatClient client = TemporalChatClient.builder(ActivityChatModel.forModel("anthropic")) - .defaultOptions(thinking) - .build(); -``` + + The pass-through relies on the `ChatOptions` subclass overriding `copy()` to return its own type — every provider class shipped with Spring AI does. @@ -170,14 +132,8 @@ Tools passed to `defaultTools()` are dispatched based on their type. The integra An interface annotated with both `@ActivityInterface` and Spring AI `@Tool` methods is auto-detected and executed as a Temporal Activity. Use this for external calls that need retries and timeouts. -```java -@ActivityInterface -public interface WeatherActivity { - @Tool(description = "Get weather for a city") - @ActivityMethod - String getWeather(String city); -} -``` + + ### Nexus service stubs @@ -187,29 +143,15 @@ Nexus service stubs with `@Tool` methods are auto-detected and invoked as [Nexus Classes annotated with `@SideEffectTool` have each `@Tool` method wrapped in `Workflow.sideEffect()`. The result is recorded in history on first execution and replayed from history afterward. Use this for cheap, non-deterministic operations such as timestamps or UUIDs. -```java -@SideEffectTool -public class TimestampTools { - @Tool(description = "Get current time") - public String now() { - return Instant.now().toString(); - } -} -``` + + ### Plain tools Any class with `@Tool` methods that isn't an Activity stub, Nexus stub, or `@SideEffectTool` runs directly on the Workflow thread. Use this for inherently deterministic tools (such as updating in-memory agent state), or for orchestration of durable primitives as you need, e.g. calling multiple Activities, child Workflows, wait conditions, or other Temporal durable primitives. -```java -public class MyTools { - @Tool(description = "Process data") - public String process(String input) { - SomeActivity act = Workflow.newActivityStub(SomeActivity.class, opts); - return act.doWork(input); - } -} -``` + + ## Use vector stores, embeddings, and MCP From a27d4329181b5d010bfb22f8091a76f8a95844c6 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Mon, 4 May 2026 13:58:56 -0400 Subject: [PATCH 09/15] edits --- docs/develop/java/integrations/spring-ai.mdx | 278 +++++++++++++++++-- 1 file changed, 249 insertions(+), 29 deletions(-) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index 8ee7e4316f..0df221c0cc 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -74,6 +74,51 @@ Use `ActivityChatModel` as a Spring AI `ChatModel` inside a Workflow. Every call Wrap `ActivityChatModel` in a `TemporalChatClient` to build prompts and register tools: +[springai/basic/src/main/java/io/temporal/samples/springai/chat/ChatWorkflowImpl.java](https://github.com/temporalio/samples-java/blob/main/springai/basic/src/main/java/io/temporal/samples/springai/chat/ChatWorkflowImpl.java) +```java +@WorkflowInit +public ChatWorkflowImpl(String systemPrompt) { + // Build an activity-backed chat model. The factory creates the activity stub + // internally and registers per-call Summaries on the Temporal UI. + ActivityChatModel activityChatModel = ActivityChatModel.forDefault(); + + // Create an activity stub for weather tools - these execute as durable activities + WeatherActivity weatherTool = + Workflow.newActivityStub( + WeatherActivity.class, + ActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofSeconds(30)) + .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(3).build()) + .build()); + + // Create deterministic tools - these execute directly in the workflow + StringTools stringTools = new StringTools(); + + // Create side-effect tools - these are wrapped in Workflow.sideEffect() + // The result is recorded in history, making replay deterministic + TimestampTools timestampTools = new TimestampTools(); + + // Create chat memory - uses in-memory storage that gets rebuilt on replay + ChatMemory chatMemory = + MessageWindowChatMemory.builder() + .chatMemoryRepository(new InMemoryChatMemoryRepository()) + .maxMessages(20) + .build(); + + // Build a TemporalChatClient with tools and memory + // - Activity stubs (weatherTool) become durable AI tools + // - plain workflow tool classes (stringTools) execute directly in workflow + // - @SideEffectTool classes (timestampTools) are wrapped in sideEffect() + // - PromptChatMemoryAdvisor maintains conversation history + this.chatClient = + TemporalChatClient.builder(activityChatModel) + .defaultSystem(systemPrompt) + .defaultTools(weatherTool, stringTools, timestampTools) + .defaultAdvisors(PromptChatMemoryAdvisor.builder(chatMemory).build()) + .build(); +} + +``` `ActivityChatModel.forDefault()` resolves to the default Spring AI `ChatModel` bean. To target a specific model in a multi-model application, pass its bean name to `ActivityChatModel.forModel("openai")`. @@ -82,6 +127,179 @@ Wrap `ActivityChatModel` in a `TemporalChatClient` to build prompts and register Streaming responses are not currently supported. ::: +## Register tools + +Tools passed to `defaultTools()` are dispatched based on their type. The integration handles Temporal determinism for you when the tool is durable, and gives you control when it isn't. + +### Activity stubs + +An interface annotated with both `@ActivityInterface` and Spring AI `@Tool` methods is auto-detected and executed as a Temporal Activity. Use this for external calls that need retries and timeouts. + + +[springai/basic/src/main/java/io/temporal/samples/springai/chat/WeatherActivity.java](https://github.com/temporalio/samples-java/blob/main/springai/basic/src/main/java/io/temporal/samples/springai/chat/WeatherActivity.java) +```java +@ActivityInterface +public interface WeatherActivity { + + /** + * Gets the current weather for a city. + * + *

The {@code @Tool} annotation makes this method available to the AI model, while the + * {@code @ActivityInterface} ensures it executes as a Temporal activity. + * + * @param city the name of the city + * @return a description of the current weather + */ + @Tool( + description = + "Get the current weather for a city. Returns temperature, conditions, and humidity.") + @ActivityMethod + String getWeather( + @ToolParam(description = "The name of the city (e.g., 'Seattle', 'New York')") String city); + + /** + * Gets the weather forecast for a city. + * + * @param city the name of the city + * @param days the number of days to forecast (1-7) + * @return the weather forecast + */ + @Tool(description = "Get the weather forecast for a city for the specified number of days.") + @ActivityMethod + String getForecast( + @ToolParam(description = "The name of the city") String city, + @ToolParam(description = "Number of days to forecast (1-7)") int days); +} +``` + + +### Nexus service stubs + +Nexus service stubs with `@Tool` methods are auto-detected and invoked as [Nexus operations](/develop/java/nexus), enabling cross-Namespace tool calls. + +### `@SideEffectTool` + +Classes annotated with `@SideEffectTool` have each `@Tool` method wrapped in `Workflow.sideEffect()`. The result is recorded in history on first execution and replayed from history afterward. Use this for cheap, non-deterministic operations such as timestamps or UUIDs. + + +[springai/basic/src/main/java/io/temporal/samples/springai/chat/TimestampTools.java](https://github.com/temporalio/samples-java/blob/main/springai/basic/src/main/java/io/temporal/samples/springai/chat/TimestampTools.java) +```java +@SideEffectTool +public class TimestampTools { + + private static final DateTimeFormatter FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z").withZone(ZoneId.systemDefault()); + + /** + * Gets the current date and time. + * + *

This is non-deterministic (returns different values each time), but wrapped in sideEffect() + * it becomes safe for workflow replay. + * + * @return the current date and time as a formatted string + */ + @Tool(description = "Get the current date and time") + public String getCurrentDateTime() { + return FORMATTER.format(Instant.now()); + } + + /** + * Gets the current Unix timestamp in milliseconds. + * + * @return the current time in milliseconds since epoch + */ + @Tool(description = "Get the current Unix timestamp in milliseconds") + public long getCurrentTimestamp() { + return System.currentTimeMillis(); + } + + /** + * Generates a random UUID. + * + * @return a new random UUID string + */ + @Tool(description = "Generate a random UUID") + public String generateUuid() { + return UUID.randomUUID().toString(); + } + + /** + * Gets the current date and time in a specific timezone. + * + * @param timezone the timezone ID (e.g., "America/New_York", "UTC", "Europe/London") + * @return the current date and time in the specified timezone + */ + @Tool(description = "Get the current date and time in a specific timezone") + public String getDateTimeInTimezone( + @ToolParam(description = "Timezone ID (e.g., 'America/New_York', 'UTC', 'Europe/London')") + String timezone) { + try { + ZoneId zoneId = ZoneId.of(timezone); + DateTimeFormatter formatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z").withZone(zoneId); + return formatter.format(Instant.now()); + } catch (Exception e) { + return "Invalid timezone: " + timezone + ". Use formats like 'America/New_York' or 'UTC'."; + } + } +} +``` + + +### Plain tools + +Any class with `@Tool` methods that isn't an Activity stub, Nexus stub, or `@SideEffectTool` runs directly on the Workflow thread. Use this for inherently deterministic tools (such as updating in-memory agent state), or for orchestration of durable primitives as you need, e.g. calling multiple Activities, child Workflows, wait conditions, or other Temporal durable primitives. + + +[springai/basic/src/main/java/io/temporal/samples/springai/chat/StringTools.java](https://github.com/temporalio/samples-java/blob/main/springai/basic/src/main/java/io/temporal/samples/springai/chat/StringTools.java) +```java +public class StringTools { + + @Tool(description = "Reverse a string, returning the characters in opposite order") + public String reverse(@ToolParam(description = "The string to reverse") String input) { + if (input == null) { + return null; + } + return new StringBuilder(input).reverse().toString(); + } + + @Tool(description = "Count the number of words in a text") + public int countWords(@ToolParam(description = "The text to count words in") String text) { + if (text == null || text.isBlank()) { + return 0; + } + return text.trim().split("\\s+").length; + } + + @Tool(description = "Convert text to all uppercase letters") + public String toUpperCase(@ToolParam(description = "The text to convert") String text) { + if (text == null) { + return null; + } + return text.toUpperCase(java.util.Locale.ROOT); + } + + @Tool(description = "Convert text to all lowercase letters") + public String toLowerCase(@ToolParam(description = "The text to convert") String text) { + if (text == null) { + return null; + } + return text.toLowerCase(java.util.Locale.ROOT); + } + + @Tool(description = "Check if a string is a palindrome (reads the same forwards and backwards)") + public boolean isPalindrome(@ToolParam(description = "The text to check") String text) { + if (text == null) { + return false; + } + String normalized = text.toLowerCase(java.util.Locale.ROOT).replaceAll("\\s+", ""); + String reversed = new StringBuilder(normalized).reverse().toString(); + return normalized.equals(reversed); + } +} +``` + + ## Activity options and retry behavior `ActivityChatModel.forDefault()` and `forModel(name)` build the chat Activity stub with sensible defaults: a 2-minute start-to-close timeout, 3 attempts, and `org.springframework.ai.retry.NonTransientAiException` and `java.lang.IllegalArgumentException` classified as non-retryable so a bad API key or invalid prompt fails fast. @@ -98,6 +316,19 @@ ActivityChatModel chatModel = ActivityChatModel.forDefault( For configuration-driven per-model overrides, declare a `ChatModelActivityOptions` bean. The plugin consults it whenever `forDefault()` or `forModel(name)` runs in a Workflow. Use the special key `ChatModelTypes.DEFAULT_MODEL_NAME` (the literal `"default"`) as a global catch-all that applies to any model not explicitly listed — including models contributed by third-party starters: +[springai/multimodel/src/main/java/io/temporal/samples/springai/multimodel/ChatModelConfig.java](https://github.com/temporalio/samples-java/blob/main/springai/multimodel/src/main/java/io/temporal/samples/springai/multimodel/ChatModelConfig.java) +```java +@Bean +public ChatModelActivityOptions chatModelActivityOptions() { + return new ChatModelActivityOptions( + Map.of( + "anthropicChatModel", + ActivityOptions.newBuilder(ActivityChatModel.defaultActivityOptions()) + .setStartToCloseTimeout(Duration.ofMinutes(5)) + .setScheduleToCloseTimeout(Duration.ofMinutes(15)) + .build())); +} +``` Keys that neither match a registered `ChatModel` bean nor equal `"default"` cause plugin construction to fail, so a typo surfaces at startup rather than at first call. @@ -109,6 +340,24 @@ Keys that neither match a registered `ChatModel` bean nor equal `"default"` caus Provider-specific `ChatOptions` subclasses — for example, `AnthropicChatOptions` to enable extended thinking, or `OpenAiChatOptions` to set `reasoning_effort` — pass through the Activity boundary unchanged. Attach them via `ChatClient.defaultOptions(...)` and the plugin re-applies them on the Activity side before calling the underlying model: +[springai/multimodel/src/main/java/io/temporal/samples/springai/multimodel/MultiModelWorkflowImpl.java](https://github.com/temporalio/samples-java/blob/main/springai/multimodel/src/main/java/io/temporal/samples/springai/multimodel/MultiModelWorkflowImpl.java) +```java +AnthropicChatOptions thinkingOptions = + AnthropicChatOptions.builder() + .thinking(AnthropicApi.ThinkingType.ENABLED, 1024) + .temperature(1.0) + .maxTokens(4096) + .build(); +chatClients.put( + "think", + TemporalChatClient.builder(anthropicModel) + .defaultSystem( + "You are a helpful assistant powered by Anthropic with extended thinking. " + + "Use the thinking budget to reason carefully, then give a crisp answer " + + "that reflects the reasoning you did.") + .defaultOptions(thinkingOptions) + .build()); +``` The pass-through relies on the `ChatOptions` subclass overriding `copy()` to return its own type — every provider class shipped with Spring AI does. @@ -124,35 +373,6 @@ Media image = new Media(MimeTypeUtils.IMAGE_PNG, URI.create("https://cdn.example Override the cap by setting the system property `io.temporal.springai.maxMediaBytes` before your worker starts (positive integer; `0` disables the check). For anything larger than a small thumbnail, route the bytes to a binary store from an Activity and pass only the URL across the conversation. -## Register tools - -Tools passed to `defaultTools()` are dispatched based on their type. The integration handles Temporal determinism for you when the tool is durable, and gives you control when it isn't. - -### Activity stubs - -An interface annotated with both `@ActivityInterface` and Spring AI `@Tool` methods is auto-detected and executed as a Temporal Activity. Use this for external calls that need retries and timeouts. - - - - -### Nexus service stubs - -Nexus service stubs with `@Tool` methods are auto-detected and invoked as [Nexus operations](/develop/java/nexus), enabling cross-Namespace tool calls. - -### `@SideEffectTool` - -Classes annotated with `@SideEffectTool` have each `@Tool` method wrapped in `Workflow.sideEffect()`. The result is recorded in history on first execution and replayed from history afterward. Use this for cheap, non-deterministic operations such as timestamps or UUIDs. - - - - -### Plain tools - -Any class with `@Tool` methods that isn't an Activity stub, Nexus stub, or `@SideEffectTool` runs directly on the Workflow thread. Use this for inherently deterministic tools (such as updating in-memory agent state), or for orchestration of durable primitives as you need, e.g. calling multiple Activities, child Workflows, wait conditions, or other Temporal durable primitives. - - - - ## Use vector stores, embeddings, and MCP When the corresponding Spring AI modules are on the classpath, the integration registers Activities for vector stores, embeddings, and MCP tool calls. Inject the matching Spring AI types into your Activities or Workflows and use them as you would in any Spring AI application — each operation is executed through a Temporal Activity. From e3d96c30f444d5c274f70c82fb97bc2ca271d0eb Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 5 May 2026 10:01:17 -0400 Subject: [PATCH 10/15] Update docs/develop/java/integrations/spring-ai.mdx Co-authored-by: Lenny Chen <55669665+lennessyy@users.noreply.github.com> --- docs/develop/java/integrations/spring-ai.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index 0df221c0cc..4747e72ad1 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -387,7 +387,7 @@ new McpPlugin(); `ActivityMcpClient` wraps a Spring AI MCP client so that remote MCP tool calls become durable Activity executions. -## Learn more +## Resources - [`temporal-spring-ai` README](https://github.com/temporalio/sdk-java/blob/master/temporal-spring-ai/README.md) — full reference for the module - [Spring Boot integration](/develop/java/integrations/spring-boot-integration) — required companion module From 6467e2defa63297d4d5f25106a225fae30c701b4 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 5 May 2026 10:02:24 -0400 Subject: [PATCH 11/15] Update docs/develop/java/integrations/spring-ai.mdx Co-authored-by: Lenny Chen <55669665+lennessyy@users.noreply.github.com> --- docs/develop/java/integrations/spring-ai.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index 4747e72ad1..9d4e13b4ae 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -124,6 +124,7 @@ public ChatWorkflowImpl(String systemPrompt) { `ActivityChatModel.forDefault()` resolves to the default Spring AI `ChatModel` bean. To target a specific model in a multi-model application, pass its bean name to `ActivityChatModel.forModel("openai")`. :::note + Streaming responses are not currently supported. ::: From a0360ea30f99840d38917ca9e25d5dd767431519 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 5 May 2026 10:02:46 -0400 Subject: [PATCH 12/15] Update docs/develop/java/integrations/spring-ai.mdx Co-authored-by: Lenny Chen <55669665+lennessyy@users.noreply.github.com> --- docs/develop/java/integrations/spring-ai.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index 9d4e13b4ae..f50f0e01a5 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -19,7 +19,9 @@ tags: description: Build durable AI agents in Java with the Temporal Spring AI integration. --- -[Spring AI](https://docs.spring.io/spring-ai/reference/) is an agent framework for Java applications — chat clients, tool calling, vector stores, embeddings, and MCP servers, all wired through Spring Boot. The [Temporal Spring AI integration](https://central.sonatype.com/artifact/io.temporal/temporal-spring-ai) makes Spring AI agents durable: model calls run through Temporal Activities recorded in Workflow history, and tools are dispatched per their type so each kind lands in the right place in Workflow execution — Activity stubs and Nexus stubs as durable operations, `@SideEffectTool` classes wrapped in `Workflow.sideEffect`, and plain tools running directly in Workflow code. Agents retry on failure and replay deterministically without changing how you write Spring AI code. +[Spring AI](https://docs.spring.io/spring-ai/reference/) is an agent framework for Java applications — chat clients, tool calling, vector stores, embeddings, and MCP servers, all wired through Spring Boot. + +The [Temporal Spring AI integration](https://central.sonatype.com/artifact/io.temporal/temporal-spring-ai) makes Spring AI agents durable: model calls run through Temporal Activities recorded in Workflow history, and tools are dispatched per their type so each kind lands in the right place in Workflow execution — Activity stubs and Nexus stubs as durable operations, `@SideEffectTool` classes wrapped in `Workflow.sideEffect`, and plain tools running directly in Workflow code. Agents retry on failure and replay deterministically without changing how you write Spring AI code. The integration is built on the Temporal Java SDK's [Plugin system](/develop/plugins-guide) and is distributed as the `io.temporal:temporal-spring-ai` module alongside the existing [Spring Boot integration](/develop/java/integrations/spring-boot-integration). From be1dad4c8e4fe69e63d7820fd65de1cae9301720 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 5 May 2026 10:05:54 -0400 Subject: [PATCH 13/15] Rename Compatibility section to Prerequisites and clarify requirements --- docs/develop/java/integrations/spring-ai.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index f50f0e01a5..986d74d680 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -32,7 +32,9 @@ The Spring AI Integration is in Public Preview. Refer to the ::: -## Compatibility +## Prerequisites + +The integration requires all of the following on your application's classpath. The plugin won't auto-configure if any of these are missing or below the listed minimum: | Dependency | Minimum version | | ----------------- | --------------- | @@ -41,6 +43,8 @@ The Spring AI Integration is in Public Preview. Refer to the | Spring AI | 1.1.0 | | Temporal Java SDK | 1.35.0 | +You also need the [`temporal-spring-boot-starter`](/develop/java/integrations/spring-boot-integration) and a Spring AI model starter (for example, `spring-ai-starter-model-openai`) — `temporal-spring-ai` does not pull in a model provider on its own. + ## Add the dependency Add `temporal-spring-ai` alongside `temporal-spring-boot-starter` and a Spring AI model starter (for example, `spring-ai-starter-model-openai`). From e1e86ae0e8a008523a6aeed2a718923c3b931a17 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 5 May 2026 10:07:02 -0400 Subject: [PATCH 14/15] Explain tool registration and link to Spring AI tools docs --- docs/develop/java/integrations/spring-ai.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index 986d74d680..534e3ecfb4 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -136,7 +136,9 @@ Streaming responses are not currently supported. ## Register tools -Tools passed to `defaultTools()` are dispatched based on their type. The integration handles Temporal determinism for you when the tool is durable, and gives you control when it isn't. +In Spring AI, [tools](https://docs.spring.io/spring-ai/reference/api/tools.html) are methods the model can choose to call to fetch data or take action — you make them available to a chat client by registering them, typically through `ChatClient.defaultTools(...)` or per-prompt `tools(...)`. The chat client advertises the methods to the model, the model decides which (if any) to call, and the framework runs the chosen method and feeds the result back into the conversation. + +The Temporal integration extends this by inspecting the type of each tool you register and dispatching it to the appropriate Temporal primitive, so you can mix durable and in-Workflow tools in the same chat client. The integration handles Temporal determinism for you when the tool is durable, and gives you control when it isn't. ### Activity stubs From 789c629ea7fa35820da7eae620d0cdfdac85fe42 Mon Sep 17 00:00:00 2001 From: Donald Pinckney Date: Tue, 5 May 2026 10:07:12 -0400 Subject: [PATCH 15/15] Cross-link heartbeats and priority docs in ActivityOptions guidance --- docs/develop/java/integrations/spring-ai.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/java/integrations/spring-ai.mdx b/docs/develop/java/integrations/spring-ai.mdx index 534e3ecfb4..ec574bfaff 100644 --- a/docs/develop/java/integrations/spring-ai.mdx +++ b/docs/develop/java/integrations/spring-ai.mdx @@ -313,7 +313,7 @@ public class StringTools { `ActivityChatModel.forDefault()` and `forModel(name)` build the chat Activity stub with sensible defaults: a 2-minute start-to-close timeout, 3 attempts, and `org.springframework.ai.retry.NonTransientAiException` and `java.lang.IllegalArgumentException` classified as non-retryable so a bad API key or invalid prompt fails fast. -Pass an `ActivityOptions` directly when you need finer control — a specific Task Queue, heartbeats, priority, or a custom `RetryOptions`: +Pass an `ActivityOptions` directly when you need finer control — a specific Task Queue, [heartbeats](/develop/java/activities/execution#heartbeattimeout), [priority](/develop/task-queue-priority-fairness), or a custom `RetryOptions`: ```java ActivityChatModel chatModel = ActivityChatModel.forDefault(