Zero-Config ACP Agents with Spring Boot

One dependency, a few properties, an annotated bean. Spring Boot handles the rest - transport, lifecycle, graceful shutdown.

Zero-Config ACP Agents with Spring Boot
Zero-Config ACP Agents - Agento relaxing while Spring beans wire themselves

ACP standardizes how IDEs talk to AI agents - LSP for agents instead of language servers. If you want the full story, see ACP Java SDK: Building IDE Agents in Java.

The ACP Java SDK already has an annotation-based programming model - annotate a class with @AcpAgent, add @Initialize, @Prompt, and @NewSession handlers, and you're done. But to use that in Spring Boot, you still need to wire the beans yourself: create the transport, build the AcpAgentSupport, manage lifecycle with SmartLifecycle, handle graceful shutdown.

That's the kind of boilerplate Spring Boot autoconfiguration exists to eliminate. So I wrote one. It's part of the spring-ai-community GitHub organization.

What you'd write without it

To run an @AcpAgent bean in Spring Boot without the starter, you'd need a configuration class like this:

@Configuration
public class AcpConfig {

    @Bean
    AcpAgentTransport acpAgentTransport() {
        return new StdioAcpAgentTransport();
    }

    @Bean
    SmartLifecycle acpAgentLifecycle(MyAgent agent,
                                     AcpAgentTransport transport) {
        var support = AcpAgentSupport.create(agent)
            .transport(transport)
            .build();
        return new SmartLifecycle() {
            private volatile boolean running = false;
            public void start() { support.start(); running = true; }
            public void stop()  { support.close(); running = false; }
            public boolean isRunning() { return running; }
        };
    }
}

Plus the same for the client side - transport detection, AcpSyncClient and AcpAsyncClient beans, DisposableBean for shutdown. That's four configuration classes worth of plumbing.

What the autoconfiguration handles

Add the starter dependency, and all of that disappears. Your agent is just a Spring bean:

@Component
@AcpAgent(name = "echo-agent", version = "1.0")
public class EchoAgentBean {

    @Initialize
    public InitializeResponse initialize(InitializeRequest request) {
        return InitializeResponse.ok();
    }

    @NewSession
    public NewSessionResponse newSession(NewSessionRequest request) {
        return new NewSessionResponse(
            UUID.randomUUID().toString(), null, null);
    }

    @Prompt
    public PromptResponse prompt(PromptRequest request,
                                 SyncPromptContext context) {
        context.sendMessage("Echo: " + request.text());
        return PromptResponse.endTurn();
    }
}

The autoconfiguration discovers it, creates the transport, wires AcpAgentSupport, and manages start/stop through SmartLifecycle. Four @AutoConfiguration classes, each backing off if you provide your own beans:

Class What it creates
AcpAgentTransportAutoConfiguration Stdio transport (default), auto-detected from properties
AcpAgentAutoConfiguration Finds your @AcpAgent bean, wires interceptors, manages lifecycle
AcpClientTransportAutoConfiguration Client-side transport (stdio or WebSocket, auto-detected)
AcpClientAutoConfiguration AcpSyncClient + AcpAsyncClient beans with graceful shutdown

Stdio gotchas

Three configuration changes are essential for stdio agents:

application.properties:

# Disable banner - stdout is reserved for the JSON-RPC protocol
spring.main.banner-mode=off

# Keep the JVM alive (no web server to block the main thread)
spring.main.keep-alive=true

Without keep-alive, the application starts the agent, then exits immediately because there's no web server holding the JVM open. Without banner-mode=off, the Spring banner writes to stdout and corrupts the JSON-RPC stream.

logback-spring.xml - redirect all logging to stderr:

<configuration>
    <appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.err</target>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="STDERR"/>
    </root>
</configuration>

Agent stdout is reserved for the protocol. All logging must go to stderr.

Client side

The starter also autoconfigures the client side - useful for testing agents or building multi-agent systems. Configure via properties:

spring.acp.client.transport.stdio.command=java
spring.acp.client.transport.stdio.args=-jar,echo-agent.jar
spring.acp.client.request-timeout=60s

Then inject and use:

@Bean
CommandLineRunner demo(AcpSyncClient client) {
    return args -> {
        client.initialize();

        String cwd = System.getProperty("user.dir");
        var session = client.newSession(
            new NewSessionRequest(cwd, List.of()));

        var response = client.prompt(new PromptRequest(
            session.sessionId(),
            List.of(new TextContent("Hello from Spring Boot client!"))));
    };
}

Transport is auto-detected. Set spring.acp.client.transport.stdio.command and you get stdio. Set spring.acp.client.transport.websocket.uri and you get WebSocket. No explicit type selector needed.

Try it

<dependency>
    <groupId>org.springaicommunity</groupId>
    <artifactId>acp-spring-boot-starter</artifactId>
    <version>0.11.0</version>
</dependency>

The ACP Java SDK tutorial has working examples: Module 23 (Spring Boot agent) and Module 24 (Spring Boot client). The tutorial docs walk through both.

Source: github.com/spring-ai-community/acp-autoconfig

Workshop

Building Agents That Actually Work

Subscribe to get the workshop materials after Spring I/O Barcelona — execution traces, lab exercises, and the full experiment-driven guide.

Learn more →