As of 0.80.0, Spring Boot support is part of the ACP Java SDK itself:
acp-spring-boot-starter and
acp-spring-boot-autoconfigure moved from org.springaicommunity (version 0.13.0 and earlier) into
com.agentclientprotocol, versioned together with the rest of the SDK. The package changed too
(com.agentclientprotocol.sdk.spring.boot.autoconfigure, replacing com.agentclientprotocol.autoconfigure);
the spring.acp.* property names did not. There is no relocation POM, so update the coordinates and
imports directly. See the 0.80.0 migration guide for the full
change.Quick Start
Add the starter dependency:Client
Configure the transport inapplication.properties and inject the client:
Agent
Annotate a Spring bean with@AcpAgent and add handler methods:
spring.main.keep-alive with it.
Set spring.acp.agent.shutdown-on-transport-end=false to keep the old behavior, where the agent
stays up after its transport ends.
Configuration Properties
Client
Setting a capability property to
true without registering its handler in an AcpClientCustomizer
fails at startup: the SDK’s client builder now requires a handler for every capability it advertises,
and the autoconfiguration’s error names the property to turn off (or the customizer to add), for
example “fs.readTextFile needs readTextFileHandler… The capabilities come from
spring.acp.client.capabilities.read-text-file=true”. The application’s own sessionUpdateHandler,
registered through a customizer, replaces the autoconfiguration’s DEBUG-logging one rather than
running alongside it.Agent
port and max-concurrent-streams-per-connection moved under transport.http.listener.*: they mean
nothing in a servlet web application (which serves the endpoint on its own server and server.port),
only for the SDK’s own standalone listener, so they live under their own prefix rather than beside the
properties both modes share.Transport Selection
The client transport is selected automatically based on which properties are set:- Set
spring.acp.client.transport.stdio.command→ stdio transport - Set
spring.acp.client.transport.websocket.uri→ WebSocket transport - Set
spring.acp.client.transport.http.uri→ Streamable HTTP transport - Set
spring.acp.client.transport.type→ explicit selection (takes precedence;httpwithout the URI property fails at startup with a message naming it)
Setting more than one of
stdio.command, websocket.uri, and http.uri with no explicit type now
fails at startup instead of silently picking one: “Several ACP client transports are configured
[STDIO, HTTP]; choose one with spring.acp.client.transport.type.” Set type, or leave only one
property set.spring.acp.agent.enabled=false to disable, or
spring.acp.agent.transport.type=http to serve it remotely; see below.
Remote Agents over Streamable HTTP
Setspring.acp.agent.transport.type=http, and add the module your kind of application needs. Every
remote connection gets its own agent runtime, created from the SDK’s AcpAgentFactory, all served by
your one @AcpAgent bean, so that bean must be thread-safe.
- Servlet web application (
spring-boot-starter-webpresent): addcom.agentclientprotocol:acp-http-servlet(no Jetty dependency). The agent mounts on the application’s own server, atspring.acp.agent.transport.http.path(default/acp), serving Streamable HTTP, SSE, and WebSocket, all onserver.port, through the application’s own filter chain: Spring Security rules on/acpapply to the WebSocket handshake too, and the authenticated principal reaches the endpoint. Thelistener.*properties are ignored in this mode. - Reactive web application (
spring-boot-starter-webfluxpresent): addcom.agentclientprotocol:acp-http-webflux. The agent mounts the same way, as aRouterFunctionbean (acpRouterFunction) onserver.port, Reactor Netty by default: Streamable HTTP, SSE and WebSocket on one path, through the application’s ownWebFilters, so aSecurityWebFilterChainapplies to/acpthe same way. Thelistener.*properties are ignored here too. - Non-web application (no
spring-boot-starter-webor-webflux): addcom.agentclientprotocol:acp-streamable-http-jetty. The SDK’s own Jetty listener opensspring.acp.agent.transport.http.listener.portand serves Streamable HTTP, a WebSocket upgrade on the same path, and cleartext HTTP/2 (h2c).
acp-http-servlet, or managing
jakarta.websocket-api to 2.2.0), instead of starting cleanly and failing every WebSocket send with
NoSuchMethodError later, with clients just hanging.
On Reactor Netty, an oversized WebSocket message is the endpoint’s own limit to enforce (closing with
1009); on Tomcat or Jetty as a WebFlux server, the container’s own, smaller message limit applies
first (8 KB on Tomcat, 64 KB on Jetty), so a larger message closes there instead. Use Reactor Netty, or
a servlet application, for large WebSocket messages.
Only ACP’s own web-socket-idle-timeout applies in servlet mode, and in reactive mode on Reactor
Netty (the default and recommended server for WebFlux): the framework’s own idle-timeout setting
doesn’t reach the upgraded socket.
Known issue (0.80.0): Jetty as a WebFlux server closes idle ACP WebSockets early. Jetty’s own
connection idle timeout (30 seconds by default) closes an ACP WebSocket after 30 seconds of no
traffic, overriding ACP’s own
web-socket-idle-timeout (30 minutes by default). Set
server.jetty.connection-idle-timeout at least as long as
spring.acp.agent.transport.http.web-socket-idle-timeout (for example 30m), or use Reactor Netty,
the default and recommended server for WebFlux. A fix is planned for 0.80.1.spring.acp.client.transport.http.uri=http://host:port/acp for
StreamableHttpAcpClientTransport, or spring.acp.client.transport.websocket.uri=ws://host:port/acp
for WebSocketAcpClientTransport; both work against the servlet-mounted agent, the WebFlux-mounted
agent, and the standalone listener alike.
The client (its transport included) is closed exactly once, bounded by its request timeout plus 10
seconds, by the autoconfiguration’s own lifecycle, not left for Spring to infer a close() call;
WebSocket and Streamable HTTP are equally safe to use from a Boot application, including closing
twice in a row (closeGracefully() then a context shutdown), which no longer sends a second close
frame or throws.
Override the StreamableHttpAcpAgentTransport bean (standalone listener), the AcpHttpEndpoint bean
(servlet or reactive mode), or the acpRouterFunction bean (reactive mode only) the same way as any
other auto-configured bean, to customize beyond what the properties expose. AcpHttpEndpoint and the
other host-contract types (AcpHttpExchange, AcpHttpReply, and the rest) are @UnstableAcpApi.
Enabling file access over HTTP (or any transport)
The client advertises no file system capability by default (spring.acp.client.capabilities.*
default to false), since the autoconfiguration registers no handler for them. Register one with an
AcpClientCustomizer and turn the capability on:
spring.acp.client.capabilities.terminal, and
applies equally whether the client is talking stdio, WebSocket, or Streamable HTTP: the capability
properties and the customizer are transport-independent.
AcpClientCustomizer and the transport-type enum it and the other properties above use are not
Spring-specific types: both live in com.agentclientprotocol.sdk.integration (AcpTransportType),
the framework-neutral module Micronaut and Quarkus build on too; see
Concepts: the framework-neutral layer.
Native integration, not a bolt-on
In servlet or reactive mode, the agent is mounted on the application’s own server: one server, one port (HTTP, SSE, and WebSocket together), one TLS setup, one lifecycle that starts and stops with the application, one place for metrics and logs, and Spring’s own threads. There’s nothing ACP-specific to configure alongside what the application already has. The application’s own HTTP metrics and tracing cover/acp automatically: a request to it is mounted on the application’s filter chain (servlet) or
WebFilter chain (reactive), so Spring’s ServerHttpObservationFilter records the standard
http.server.requests observation for it, method and status included, the same as any other endpoint,
with no ACP-specific configuration. Proven by the SDK’s own tests, with a TestObservationRegistry
against a real request, on both modes.
If you need it: securing /acp
Most ACP use (an IDE talking stdio, a handful of local sessions) needs no security configuration at
all. If you do expose the agent remotely, it’s mounted behind Spring’s own request handling, so
ordinary Spring Security applies to /acp the same as any other endpoint, the WebSocket handshake
included, and the authenticated principal reaches the endpoint
(AcpHttpExchange.principal()). Proven by the SDK’s own shared transport TCK running against Spring
MVC, and a matching test for the reactive host: an unauthenticated request gets 401 on HTTP and on the
WebSocket handshake, an authenticated one gets 200/101, same as any other secured endpoint.
SecurityWebFilterChain:
/acp from CSRF as shown above; the endpoint refuses a browser
request from a foreign origin on its own, regardless (see
Transports).
Extension Beans
AcpInterceptor, ArgumentResolver, and ReturnValueHandler beans are added to the agent, in bean
order, the same mechanism as the AcpClientCustomizer beans on the client side.
Where handlers run
spring.acp.agent.handler-executor names an Executor bean the agent’s handler methods run on (a
plain TaskExecutor is adapted); none means the SDK’s own pool explicitly. Left unset, the context’s
applicationTaskExecutor is used, but only when spring.threads.virtual.enabled=true (a virtual
thread per handler): without virtual threads that executor is a pool of 8 threads by default, which
would cap the prompts served at once, so the SDK’s own pool (see
Concepts: Where handlers run) stays the default
instead; name the bean explicitly to use it anyway.
Logging and sensitive data
See Transports: Logging and sensitive data for what the SDK logs at each level. Without a session-update handler of your own, session updates are logged at DEBUG, content included. A stdio agent’s standard error is logged at INFO, unfiltered, oncom.agentclientprotocol.sdk.client.transport.agent-stderr; silence it with
logging.level.com.agentclientprotocol.sdk.client.transport.agent-stderr=WARN.
Overriding Beans
All auto-configured beans back off when you provide your own. Define a customAcpClientTransport, AcpSyncClient, AcpAsyncClient, AcpAgentTransport, AcpAgentFactory, StreamableHttpAcpAgentTransport, or acpServletRegistration bean and the autoconfiguration will use yours instead.
A transport carries one client. The auto-configured AcpSyncClient wraps the AcpAsyncClient bean (new AcpSyncClient(asyncClient)), and a custom sync client should do the same. Building a second client from the transport fails at construction on ACP Java SDK 0.80.0.
Tutorial
Spring Boot Agent
Build an ACP agent as a Spring Boot application
Spring Boot Client
Use the autoconfigured ACP client in Spring Boot
Related
Micronaut Integration
The equivalent integration for Micronaut applications
Quarkus Integration
The equivalent integration for Quarkus applications
Clients and Agents in Java
The annotation model this module builds on
Transports
Where ACP listens in every deployment shape, Spring Boot included
Requirements
- Java 17+
- Spring Boot 4.1
- ACP Java SDK 0.80.0 (as of 0.80.0,
acp-spring-boot-starterandacp-spring-boot-autoconfigureare modules of the SDK itself, built and versioned together with it, not a separate project pinning an SDK version)
Resources
- GitHub Repository: source code (the
acp-spring-boot-starterandacp-spring-boot-autoconfiguremodules) - ACP Java SDK: the underlying SDK
- Maven Central: published artifacts