Documentation: https://springaicommunity.mintlify.app/acp-java-sdk | API Reference | Tutorial
Pure Java implementation of the Agent Client Protocol (ACP) specification for building both clients and agents.
The Agent Client Protocol (ACP) standardizes communication between code editors and coding agents. This SDK provides two modules:
- Client — connect to and interact with ACP-compliant agents
- Agent — build ACP-compliant agents in Java
Three API styles for building agents:
| Style | Best for | Example |
|---|---|---|
| Annotation-based | Least boilerplate — @AcpAgent, @Prompt annotations | Jump to example |
| Sync | Simple blocking handlers with plain return values | Jump to example |
| Async | Reactive applications using Project Reactor Mono | Jump to example |
Key Features:
- Java 17+, type-safe, stdio and WebSocket transports
- Capability negotiation and structured error handling
- For a hands-on walkthrough, see the ACP Java Tutorial
<dependency>
<groupId>com.agentclientprotocol</groupId>
<artifactId>acp-core</artifactId>
<version>0.14.0</version>
</dependency>For annotation-based agent development:
<dependency>
<groupId>com.agentclientprotocol</groupId>
<artifactId>acp-agent-support</artifactId>
<version>0.14.0</version>
</dependency>For WebSocket server support (agents accepting WebSocket connections):
<dependency>
<groupId>com.agentclientprotocol</groupId>
<artifactId>acp-websocket-jetty</artifactId>
<version>0.14.0</version>
</dependency>For snapshot builds (unreleased features), add the snapshot repository and use 0.15.0-SNAPSHOT:
<repositories>
<repository>
<id>central-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<snapshots><enabled>true</enabled></snapshots>
<releases><enabled>false</enabled></releases>
</repository>
</repositories>Connect to an ACP agent and send a prompt (tutorial):
importcom.agentclientprotocol.sdk.client.*;
importcom.agentclientprotocol.sdk.client.transport.*;
importcom.agentclientprotocol.sdk.spec.AcpSchema.*;
importjava.util.List;
// Launch Gemini CLI as an ACP agent subprocessvarparams = AgentParameters.builder("gemini").arg("--experimental-acp").build();
vartransport = newStdioAcpClientTransport(params);
// Create client — sessionUpdateConsumer prints the agent's streamed responseAcpSyncClientclient = AcpClient.sync(transport)
.sessionUpdateConsumer(notification -> {
if (notification.update() instanceofAgentMessageChunkmsg) {
System.out.print(((TextContent) msg.content()).text());
}
})
.build();
// Three-phase lifecycle: initialize → session → promptclient.initialize();
varsession = client.newSession(newNewSessionRequest("/workspace", List.of()));
varresponse = client.prompt(newPromptRequest(
session.sessionId(),
List.of(newTextContent("What is 2+2? Reply with just the number."))
));
// Output: 4// Stop reason: END_TURNSystem.out.println("\nStop reason: " + response.stopReason());
client.close();The simplest way to build an agent — use annotations (tutorial):
importcom.agentclientprotocol.sdk.annotation.*;
importcom.agentclientprotocol.sdk.agent.SyncPromptContext;
importcom.agentclientprotocol.sdk.agent.support.AcpAgentSupport;
importcom.agentclientprotocol.sdk.spec.AcpSchema.*;
@AcpAgentclassHelloAgent {
@InitializeInitializeResponseinit() {
returnInitializeResponse.ok();
}
@NewSessionNewSessionResponsenewSession() {
returnnewNewSessionResponse(UUID.randomUUID().toString(), null, null);
}
@PromptPromptResponseprompt(PromptRequestreq, SyncPromptContextctx) {
ctx.sendMessage("Hello from the agent!");
returnPromptResponse.endTurn();
}
}
// Bootstrap and runAcpAgentSupport.create(newHelloAgent())
.transport(newStdioAcpAgentTransport())
.run();The builder API with blocking handlers and plain return values (tutorial):
importcom.agentclientprotocol.sdk.agent.*;
importcom.agentclientprotocol.sdk.agent.transport.*;
importcom.agentclientprotocol.sdk.spec.AcpSchema.*;
importjava.util.UUID;
vartransport = newStdioAcpAgentTransport();
// Sync agent — plain return values, no MonoAcpSyncAgentagent = AcpAgent.sync(transport)
.initializeHandler(req -> InitializeResponse.ok())
.newSessionHandler(req ->
newNewSessionResponse(UUID.randomUUID().toString(), null, null))
.promptHandler((req, context) -> {
context.sendMessage("Hello from the agent!"); // blocking void methodreturnPromptResponse.endTurn();
})
.build();
agent.run(); // Blocks until client disconnectsFor reactive applications, use the async API with Project Reactor (tutorial):
importcom.agentclientprotocol.sdk.agent.*;
importcom.agentclientprotocol.sdk.agent.transport.*;
importcom.agentclientprotocol.sdk.spec.AcpSchema.*;
importreactor.core.publisher.Mono;
importjava.util.UUID;
vartransport = newStdioAcpAgentTransport();
// Async agent — handlers return MonoAcpAsyncAgentagent = AcpAgent.async(transport)
.initializeHandler(req -> Mono.just(InitializeResponse.ok()))
.newSessionHandler(req -> Mono.just(
newNewSessionResponse(UUID.randomUUID().toString(), null, null)))
.promptHandler((req, context) ->
context.sendMessage("Hello from the agent!")
.then(Mono.just(PromptResponse.endTurn())))
.build();
agent.start().then(agent.awaitTermination()).block();Send real-time updates to the client during prompt processing (tutorial: client-side, agent-side).
Annotation-based:
@PromptPromptResponseprompt(PromptRequestreq, SyncPromptContextctx) {
ctx.sendThought("Thinking...");
ctx.sendMessage("Here's my response.");
returnPromptResponse.endTurn();
}Sync:
.promptHandler((req, context) -> {
context.sendThought("Thinking...");
context.sendMessage("Here's my response.");
returnPromptResponse.endTurn();
})Async:
.promptHandler((req, context) ->
context.sendThought("Thinking...")
.then(context.sendMessage("Here's my response."))
.then(Mono.just(PromptResponse.endTurn())))Client - receiving updates:
AcpSyncClientclient = AcpClient.sync(transport)
.sessionUpdateConsumer(notification -> {
varupdate = notification.update();
if (updateinstanceofAgentMessageChunkmsg) {
System.out.print(((TextContent) msg.content()).text());
}
})
.build();Agents can request file operations from the client (tutorial). The context parameter provides access to all agent capabilities.
Agent (Sync) - reading files:
AcpSyncAgentagent = AcpAgent.sync(transport)
.promptHandler((req, context) -> {
// Convenience methods on SyncPromptContextStringcontent = context.readFile("pom.xml");
context.writeFile("output.txt", "Hello!");
returnPromptResponse.endTurn();
})
.build();
agent.run();Client - registering file handlers:
AcpSyncClientclient = AcpClient.sync(transport)
.readTextFileHandler((ReadTextFileRequestreq) -> {
// Handlers receive typed requests directlyStringcontent = Files.readString(Path.of(req.path()));
returnnewReadTextFileResponse(content);
})
.writeTextFileHandler((WriteTextFileRequestreq) -> {
Files.writeString(Path.of(req.path()), req.content());
returnnewWriteTextFileResponse();
})
.build();Check what features the peer supports before using them (tutorial):
// Client: check agent capabilities after initializeclient.initialize(newInitializeRequest(1, clientCaps));
NegotiatedCapabilitiesagentCaps = client.getAgentCapabilities();
if (agentCaps.supportsLoadSession()) {
// Agent supports session persistence
}
if (agentCaps.supportsImageContent()) {
// Agent can handle image content in prompts
}// Agent: check client capabilities before requesting operationsNegotiatedCapabilitiesclientCaps = agent.getClientCapabilities();
if (clientCaps.supportsReadTextFile()) {
agent.readTextFile(...);
} else {
// Client doesn't support file reading - handle gracefully
}
// Or use require methods (throws AcpCapabilityException if not supported)clientCaps.requireWriteTextFile();
agent.writeTextFile(...);Handle protocol errors with structured exceptions (tutorial):
importcom.agentclientprotocol.sdk.error.*;
try {
client.prompt(request);
} catch (AcpProtocolExceptione) {
if (e.isConcurrentPrompt()) {
// Another prompt is already in progress
} elseif (e.isMethodNotFound()) {
// Agent doesn't support this method
}
System.err.println("Error " + e.getCode() + ": " + e.getMessage());
} catch (AcpCapabilityExceptione) {
// Tried to use a capability the peer doesn't supportSystem.err.println("Capability not supported: " + e.getCapability());
} catch (AcpConnectionExceptione) {
// Transport-level connection error
}Use WebSocket instead of stdio for network-based communication:
Client (JDK-native, no extra dependencies):
importcom.agentclientprotocol.sdk.client.transport.WebSocketAcpClientTransport;
importjava.net.URI;
vartransport = newWebSocketAcpClientTransport(
URI.create("ws://localhost:8080/acp"),
AcpJsonMapper.createDefault()
);
AcpSyncClientclient = AcpClient.sync(transport).build();Agent (requires acp-websocket-jetty module):
importcom.agentclientprotocol.sdk.agent.transport.WebSocketAcpAgentTransport;
vartransport = newWebSocketAcpAgentTransport(
8080, // port"/acp", // pathAcpJsonMapper.createDefault()
);
AcpAsyncAgentagent = AcpAgent.async(transport)
// ... handlers ...
.build();
agent.start().block(); // Starts WebSocket server on port 8080| Package | Description |
|---|---|
com.agentclientprotocol.sdk.spec | Protocol types (AcpSchema.*) |
com.agentclientprotocol.sdk.client | Client SDK (AcpClient, AcpAsyncClient, AcpSyncClient) |
com.agentclientprotocol.sdk.agent | Agent SDK (AcpAgent, AcpAsyncAgent, AcpSyncAgent) |
com.agentclientprotocol.sdk.agent.support | Annotation-based agent runtime (AcpAgentSupport) |
com.agentclientprotocol.sdk.annotation | Agent annotations (@AcpAgent, @Prompt, etc.) |
com.agentclientprotocol.sdk.capabilities | Capability negotiation (NegotiatedCapabilities) |
com.agentclientprotocol.sdk.error | Exceptions (AcpProtocolException, AcpCapabilityException) |
| Artifact | Description |
|---|---|
acp-core | Client and Agent SDKs, stdio and WebSocket client transports |
acp-annotations | @AcpAgent, @Prompt, and other annotations |
acp-agent-support | Annotation-based agent runtime |
acp-test | In-memory transport and mock utilities for testing |
acp-websocket-jetty | Jetty-based WebSocket server transport for agents |
| Transport | Client | Agent | Module |
|---|---|---|---|
| Stdio | StdioAcpClientTransport | StdioAcpAgentTransport | acp-core |
| WebSocket | WebSocketAcpClientTransport | WebSocketAcpAgentTransport | acp-core / acp-websocket-jetty |
./mvnw compile # Compile
./mvnw test# Run tests
./mvnw verify # Run unit tests + integration tests
./mvnw install # Install to local Maven repositoryIntegration tests connect to real ACP agents and require additional setup:
# Gemini CLI integration tests (requires API key and gemini CLI)export GEMINI_API_KEY=your_key_here
./mvnw verify -pl acp-coreTest Categories:
| Type | Command | Count | Requirements |
|---|---|---|---|
| Unit tests | ./mvnw test | 370+ | None |
| Clean shutdown IT | ./mvnw verify | 4 | None |
| Gemini CLI IT | ./mvnw verify | 5 | GEMINI_API_KEY, gemini CLI in PATH |
Use the mock utilities for testing:
importcom.agentclientprotocol.sdk.test.*;
// Create in-memory transport pair for testingInMemoryTransportPairpair = InMemoryTransportPair.create();
// Use pair.clientTransport() for client, pair.agentTransport() for agentMockAcpClientmockClient = MockAcpClient.builder(pair.clientTransport())
.fileContent("/test.txt", "test content")
.build();For a hands-on, progressive introduction to the SDK, see the ACP Java Tutorial -- 30 modules covering client basics, agent development, streaming, testing, and IDE integration.
This SDK is part of the Agent Client Protocol ecosystem.
Other ACP SDKs:Kotlin | Python | TypeScript | Rust
Editor ACP docs:Zed | JetBrains | VS Code
ACP directories:Agents | Clients | Protocol spec
This SDK tracks the ACP protocol, which is evolving. We don't promise strict semver — when the protocol changes, the SDK changes. What we do commit to:
- Every breaking change is documented in release notes. Nothing breaks silently.
- Deprecate before remove where feasible.
@UnstableAcpApimarks protocol elements fromschema.unstable.json— the most likely to change. But even stable APIs may change when the protocol requires it.
If you need a stable target, pin to an exact version.
0.14.0 (Current — Maven Central)
Protocol currency: catching up to ACP spec v0.13.6 (June 2026). Supersedes the never-published 0.13.0.
New stable methods: logout, session/delete; the session config-option API (session/set_config_option, session/set_mode) is promoted to stable.
New unstable methods (marked @UnstableAcpApi): provider configuration — providers/list, providers/set, providers/disable.
Also: additionalDirectories workspace roots on session requests; per-chunk messageId on streamed chunks with sendMessage/sendThought overloads. The session-model API (session/set_model) is deprecated for removal — use session/set_config_option with a "model" category instead. Jackson aligned to 2.21.2; WebSocket max message size raised to 4 MB.
0.12.0 (Maven Central)
New stable methods: session/list, session/close, session/resume (tutorial)
New unstable methods (marked @UnstableAcpApi): elicitation/create, elicitation/complete (tutorial), session/fork, session/set_config_option
New @UnstableAcpApi annotation for protocol stability signaling (CLASS retention, aligns with JDK @PreviewFeature)
- Client and Agent SDKs with async/sync APIs
- Stdio and WebSocket transports
- Capability negotiation, structured error handling
- Full protocol compliance (all SessionUpdate types, MCP configs,
_metaextensibility)