> ## Documentation Index
> Fetch the complete documentation index at: https://lab.pollack.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# ACP Java SDK

> A Java SDK for the Agent Client Protocol — build both clients and agents that work with Zed, JetBrains, VS Code, and any ACP-compliant editor.

[![CI](https://img.shields.io/github/actions/workflow/status/agentclientprotocol/java-sdk/ci.yml?branch=main\&label=CI)](https://github.com/agentclientprotocol/java-sdk/actions/workflows/ci.yml)
[![Cross-SDK nightly](https://img.shields.io/github/actions/workflow/status/agentclientprotocol/java-sdk/cross-sdk.yml?branch=main\&label=cross-SDK%20nightly)](https://github.com/agentclientprotocol/java-sdk/actions/workflows/cross-sdk.yml)
[![Dependency scan](https://img.shields.io/github/actions/workflow/status/agentclientprotocol/java-sdk/dependency-scan.yml?branch=main\&label=dependency%20scan)](https://github.com/agentclientprotocol/java-sdk/actions/workflows/dependency-scan.yml)
[![Java interop](https://img.shields.io/github/actions/workflow/status/agentclientprotocol/java-sdk/interop-java.yml?branch=main\&label=Java%20interop)](https://github.com/agentclientprotocol/java-sdk/actions/workflows/interop-java.yml)
[![Maven Central](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Frepo1.maven.org%2Fmaven2%2Fcom%2Fagentclientprotocol%2Facp-core%2Fmaven-metadata.xml\&label=Maven%20Central)](https://central.sonatype.com/artifact/com.agentclientprotocol/acp-core)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/agentclientprotocol/java-sdk/blob/main/LICENSE)
[![Java 17+](https://img.shields.io/badge/Java-17%2B-blue)](https://github.com/agentclientprotocol/java-sdk/blob/main/pom.xml)

A pure Java implementation of the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/) specification. Build clients that connect to ACP agents, or build agents that run inside code editors.

## Quality Gates

Each badge links to the configuration that enforces it.

| Gate | What it guarantees |
| - | - |
| [![NullAway](https://img.shields.io/badge/NullAway-JSpecify%20%C2%B7%20%40NullMarked-brightgreen)](https://github.com/agentclientprotocol/java-sdk/blob/main/pom.xml) | Every SDK package is `@NullMarked`; a possible null dereference is a compile error (JDK 21+ build, `errorprone` profile). |
| [![Error Prone](https://img.shields.io/badge/Error%20Prone-warnings%20fail-brightgreen)](https://github.com/agentclientprotocol/java-sdk/blob/main/pom.xml) | Error Prone's default checks, plus unused Reactor results and stray `System.out`, fail compilation. |
| [![ArchUnit](https://img.shields.io/badge/ArchUnit-every%20module%20%C2%B7%20no%20cycles-brightgreen)](https://github.com/agentclientprotocol/java-sdk/blob/main/acp-core/src/test/java/com/agentclientprotocol/sdk/ArchitectureTest.java) | Each module's layering is tested; no package cycles, and transports never reach session internals. |
| [![PMD](https://img.shields.io/badge/PMD-complexity%20%2B%20duplication%20%C2%B7%20no%20baseline-brightgreen)](https://github.com/agentclientprotocol/java-sdk/blob/main/config/pmd/ruleset.xml) | Methods stay below cognitive and cyclomatic complexity limits; copy-paste over 100 tokens fails. |
| [![SpotBugs](https://img.shields.io/badge/SpotBugs-rank%20%E2%89%A4%209%20%2B%20concurrency-brightgreen)](https://github.com/agentclientprotocol/java-sdk/blob/main/config/spotbugs/spotbugs-include.xml) | Every bug at rank 9 or worse, and the multithreading bug patterns at any rank, fail the build. |
| [![JaCoCo](https://img.shields.io/badge/JaCoCo-acp--core%2082%25%20line%20%C2%B7%2070%25%20branch-brightgreen)](https://github.com/agentclientprotocol/java-sdk/blob/main/acp-core/pom.xml) | Each module has line and branch coverage floors in its `pom.xml`; coverage cannot silently drop. |
| [![Lincheck](https://img.shields.io/badge/Lincheck-model--checked%20concurrency-brightgreen)](https://github.com/agentclientprotocol/java-sdk/blob/main/acp-core/src/test/java/com/agentclientprotocol/sdk/spec/ActivePromptsLincheckTest.java) | The concurrent core (prompt turns, pending responses, inbound requests, SSE streams) is model-checked for linearizability. |
| [![PIT](https://img.shields.io/badge/PIT-protocol%20core%20%E2%89%A5%2085%25-brightgreen)](https://github.com/agentclientprotocol/java-sdk/blob/main/acp-core/pom.xml) | Mutation testing proves the protocol core's tests catch at least 85% of injected bugs. |
| [![OSV scan](https://img.shields.io/badge/OSV-dependency%20scan-brightgreen)](https://github.com/agentclientprotocol/java-sdk/blob/main/.github/workflows/dependency-scan.yml) | Resolved dependencies are scanned weekly and on every pom change; CVSS 7 or higher (or unscored) fails. |
| [![Cross-SDK interop](https://img.shields.io/badge/interop-TypeScript%20%C2%B7%20Rust%20%C2%B7%20Python%20%C2%B7%20Kotlin%20%C3%97%20stdio%20%C2%B7%20HTTP%20%C2%B7%20WebSocket-brightgreen)](https://github.com/agentclientprotocol/java-sdk/blob/main/integration-testing/README.md) | The Java client and agent talk to the other ACP SDKs over every transport each supports (Kotlin: stdio and WebSocket). |

## How ACP Works

ACP uses a subprocess model by default. A **client** (your application, or an editor like Zed) launches an **agent** as a child process and communicates over stdin/stdout using JSON-RPC messages. An agent can also run remotely and accept connections over HTTP or WebSocket (see [Remote agents](#remote-agents-over-streamable-http)); the messages are the same. The protocol has three phases:

1. **Initialize** — client and agent exchange protocol versions and capabilities
2. **Session** — client creates a session with a working directory context
3. **Prompt** — client sends messages, agent streams back responses

export const ProtocolAnimation = () => {
  const containerRef = React.useRef(null);
  const [status, setStatus] = React.useState("loading");
  React.useEffect(() => {
    let tweens = [];
    function start() {
      fetch("/images/acp-protocol.svg").then(res => res.text()).then(svgText => {
        if (!containerRef.current) return;
        containerRef.current.innerHTML = svgText;
        const svg = containerRef.current.querySelector("svg");
        if (!svg) {
          setStatus("error");
          return;
        }
        svg.style.width = "100%";
        svg.style.height = "auto";
        if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
          setStatus("ready");
          return;
        }
        const paths = svg.querySelectorAll(".messageLine0, .messageLine1");
        const colors = ["#00e5ff", "#69f0ae", "#00e5ff", "#69f0ae", "#00e5ff", "#ffab40", "#00e5ff", "#69f0ae", "#ff4081"];
        if (typeof gsap !== "undefined" && paths.length > 0) {
          const tl = gsap.timeline({
            repeat: -1,
            repeatDelay: 3
          });
          paths.forEach((path, index) => {
            const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
            dot.setAttribute("r", "6");
            dot.setAttribute("fill", colors[index % colors.length]);
            dot.style.filter = "drop-shadow(0 0 4px " + colors[index % colors.length] + ")";
            dot.style.opacity = "0";
            svg.appendChild(dot);
            const length = path.getTotalLength();
            const pos = {
              t: 0
            };
            const offset = index * 0.5;
            tl.set(dot, {
              opacity: 1
            }, offset);
            tl.fromTo(pos, {
              t: 0
            }, {
              t: 1,
              duration: 1.0,
              ease: "power1.inOut",
              onUpdate: function () {
                var point = path.getPointAtLength(pos.t * length);
                dot.setAttribute("cx", point.x);
                dot.setAttribute("cy", point.y);
              }
            }, offset);
            tl.to(dot, {
              opacity: 0,
              duration: 0.3
            }, offset + 1.0);
          });
          tweens.push(tl);
        }
        setStatus("ready");
      }).catch(() => setStatus("error"));
    }
    if (typeof gsap !== "undefined") {
      start();
    } else {
      window.addEventListener("gsap-ready", start, {
        once: true
      });
      setTimeout(start, 2000);
    }
    return () => tweens.forEach(t => t.kill());
  }, []);
  return <div style={{
    background: "#161b22",
    borderRadius: "8px",
    padding: "1.5rem",
    border: "1px solid #30363d",
    margin: "1rem 0"
  }}>
      {status === "loading" && <div style={{
    color: "#8b949e",
    textAlign: "center",
    padding: "2rem"
  }}>Loading diagram...</div>}
      {status === "error" && <div style={{
    color: "#ff6b6b",
    textAlign: "center",
    padding: "2rem"
  }}>Failed to load diagram</div>}
      <div ref={containerRef} />
      {status === "ready" && <div style={{
    display: "flex",
    gap: "1.5rem",
    flexWrap: "wrap",
    marginTop: "0.75rem",
    fontSize: "0.8rem",
    color: "#8b949e"
  }}>
          <span><span style={{
    color: "#00e5ff"
  }}>●</span> request</span>
          <span><span style={{
    color: "#69f0ae"
  }}>●</span> response</span>
          <span><span style={{
    color: "#ffab40"
  }}>●</span> streaming update</span>
          <span><span style={{
    color: "#ff4081"
  }}>●</span> agent → client request</span>
        </div>}
    </div>;
};

<ProtocolAnimation />

This is the same mechanism that Zed, JetBrains, and VS Code use to talk to AI agents. The SDK lets you build either side of that conversation.

## Overview

The ACP Java SDK provides:

* **Client SDK** — connect to and interact with any ACP-compliant agent
* **Agent SDK** — build ACP-compliant agents that work in Zed, JetBrains, and VS Code
* **Test utilities** — in-memory transports for fast, deterministic testing

## Quick Start

### Try it now

The fastest way to see ACP in action is to clone the tutorial and run a module. The client example talks to the [Grok CLI](https://github.com/xai-org/grok-cli) (signed in once with `grok login`; no API key needed). The agent example runs locally with no API key either.

```bash theme={null}
git clone https://github.com/markpollack/acp-java-tutorial.git
cd acp-java-tutorial

# Client: talk to Grok as an ACP agent
./mvnw exec:java -pl module-01-first-contact

# Agent: build and run your own (no API key needed)
./mvnw package -pl module-12-echo-agent -q
./mvnw exec:java -pl module-12-echo-agent
```

### Client — Connect to an Agent

This example launches the [Grok CLI](https://github.com/xai-org/grok-cli) as an ACP agent subprocess and sends it a prompt. Any CLI tool that speaks ACP over stdin/stdout works here; Grok is one such tool.

`AgentParameters` builds the command line (`grok agent stdio`). `StdioAcpClientTransport` spawns the process and handles the JSON-RPC framing. The `sessionUpdateHandler` receives the agent's response text as it streams in — without it, you'd get the stop reason but no visible output.

```java theme={null}
import com.agentclientprotocol.sdk.client.*;
import com.agentclientprotocol.sdk.client.transport.*;
import com.agentclientprotocol.sdk.spec.AcpSchema.*;
import java.util.List;

// Launch the Grok CLI as an ACP agent subprocess
var params = AgentParameters.builder("grok").arg("agent").arg("stdio").build();
var transport = new StdioAcpClientTransport(params);

AcpSyncClient client = AcpClient.sync(transport)
    .sessionUpdateHandler(notification -> {
        // Print the agent's response as it streams in
        if (notification.update() instanceof AgentMessageChunk msg) {
            System.out.print(((TextContent) msg.content()).text());
        }
    })
    .build();

client.initialize();
// ACP requires cwd to be an absolute path; agents may reject "."
var session = client.newSession(new NewSessionRequest(System.getProperty("user.dir"), List.of()));

var response = client.prompt(new PromptRequest(
    session.sessionId(),
    List.of(new TextContent("What is 2+2? Reply with just the number."))
));

System.out.println("\nStop reason: " + response.stopReason());
client.close();
// Output: 4
// Stop reason: end_turn
```

<Tip>
  Run this yourself: [Module 01: First Contact](https://github.com/markpollack/acp-java-tutorial/tree/main/module-01-first-contact) — full source with error handling and setup.
</Tip>

### Agent — Build Your Own

This is the other side of the conversation. When you build an agent, **editors and clients launch your code** as a subprocess and send it JSON-RPC messages over stdin. Your agent handles three request types: initialize, new session, and prompt.

The stdio transport reads from stdin and writes to stdout. `run()` blocks until the client disconnects. The module includes a demo client that launches this agent as a subprocess and exercises it — you'll see `Echo: Hello!` printed when you run it.

```java theme={null}
import com.agentclientprotocol.sdk.annotation.*;
import com.agentclientprotocol.sdk.agent.SyncPromptContext;
import com.agentclientprotocol.sdk.agent.support.AcpAgentSupport;
import com.agentclientprotocol.sdk.agent.transport.StdioAcpAgentTransport;
import com.agentclientprotocol.sdk.spec.AcpSchema.*;
import java.util.UUID;

@AcpAgent
class EchoAgent {

    @Initialize
    InitializeResponse init() {
        return InitializeResponse.ok();
    }

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

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

// Reads JSON-RPC from stdin, writes to stdout — editors connect here
AcpAgentSupport.create(new EchoAgent())
    .transport(new StdioAcpAgentTransport())
    .build()
    .run();
```

<Tip>
  Run this yourself: [Module 12: Echo Agent](https://github.com/markpollack/acp-java-tutorial/tree/main/module-12-echo-agent) — includes a demo client that launches the agent and sends test prompts. No API key required.
</Tip>

### Adding the SDK to your project

`acp-core` holds the client and agent SDKs and the stdio, WebSocket, and Streamable HTTP client transports. From 0.18.0 it contains no JSON implementation, so add one JSON module next to it:

```xml theme={null}
<dependency>
    <groupId>com.agentclientprotocol</groupId>
    <artifactId>acp-core</artifactId>
    <version>0.80.0</version>
</dependency>
<!-- Jackson 2 (com.fasterxml.jackson.databind) -->
<dependency>
    <groupId>com.agentclientprotocol</groupId>
    <artifactId>acp-json-jackson2</artifactId>
    <version>0.80.0</version>
</dependency>
<!-- or Jackson 3 (tools.jackson.databind), for example alongside Spring Boot 4 -->
<!--
<dependency>
    <groupId>com.agentclientprotocol</groupId>
    <artifactId>acp-json-jackson3</artifactId>
    <version>0.80.0</version>
</dependency>
-->
```

<Warning>
  **Upgrading from 0.17 or earlier on `acp-core` alone?** Without a JSON module, `AcpJsonMapper.createDefault()` fails at runtime with `No AcpJsonMapperSupplier found on the classpath`. `acp-agent-support`, `acp-test`, and `acp-streamable-http-jetty` already bring `acp-json-jackson2`, so users of those modules need nothing more. Both JSON modules write the same bytes on the wire; with both on the classpath, Jackson 3 wins.
</Warning>

<Note>
  0.80.0 runs on the classpath and is not yet JPMS-modular (no `module-info`): two packages,
  `com.agentclientprotocol.sdk.agent.transport` and `com.agentclientprotocol.sdk.json`, are split across
  more than one jar (`acp-core` plus `acp-streamable-http-jetty`, and `acp-core` plus
  `acp-json-jackson2`/`acp-json-jackson3`, respectively), which JPMS doesn't allow for one package. A fix
  is planned after 0.80.0.
</Note>

For annotation-based agents (as shown above), add `acp-agent-support` instead — it includes `acp-core` and `acp-json-jackson2` transitively:

```xml theme={null}
<dependency>
    <groupId>com.agentclientprotocol</groupId>
    <artifactId>acp-agent-support</artifactId>
    <version>0.80.0</version>
</dependency>
```

### Remote agents over Streamable HTTP

An agent does not have to be a subprocess. From 0.18.0 the SDK implements the ACP [Streamable HTTP and WebSocket transport](https://agentclientprotocol.com/rfds/streamable-http-websocket-transport): the agent listens on an HTTP endpoint, and any number of clients connect to it over HTTP POST with Server-Sent Event streams, or over a WebSocket upgrade on the same path. Each connection gets its own agent. Plain `http://` (for example on localhost) is a first-class deployment.

**Agent** — add `acp-streamable-http-jetty`:

```xml theme={null}
<dependency>
    <groupId>com.agentclientprotocol</groupId>
    <artifactId>acp-streamable-http-jetty</artifactId>
    <version>0.80.0</version>
</dependency>
```

`StreamableHttpAcpAgentTransport` runs an embedded Jetty listener and creates one fresh agent per remote connection through an `AcpAgentFactory`. The agent definition is the same builder you would use over stdio:

```java theme={null}
import com.agentclientprotocol.sdk.agent.AcpAgent;
import com.agentclientprotocol.sdk.agent.AcpAgentFactory;
import com.agentclientprotocol.sdk.agent.transport.StreamableHttpAcpAgentTransport;
import com.agentclientprotocol.sdk.json.AcpJsonMapper;
import com.agentclientprotocol.sdk.spec.AcpSchema.*;
import java.util.UUID;

// One fresh agent per remote connection
AcpAgentFactory agents = AcpAgentFactory.sync(transport -> AcpAgent.sync(transport)
    .initializeHandler(req -> InitializeResponse.ok())
    .newSessionHandler(req -> new NewSessionResponse(UUID.randomUUID().toString(), null, null))
    .promptHandler((req, ctx) -> {
        ctx.sendMessage("Echo: " + req.text());
        return PromptResponse.endTurn();
    })
    .build());

var server = new StreamableHttpAcpAgentTransport(8080, AcpJsonMapper.createDefault(), agents);
server.start().block();            // serves http://localhost:8080/acp and ws://localhost:8080/acp
server.awaitTermination().block();
```

**Client** — `StreamableHttpAcpClientTransport` is in `acp-core`. Everything after the transport is the same client code as over stdio:

```java theme={null}
import com.agentclientprotocol.sdk.client.AcpClient;
import com.agentclientprotocol.sdk.client.AcpSyncClient;
import com.agentclientprotocol.sdk.client.transport.StreamableHttpAcpClientTransport;
import com.agentclientprotocol.sdk.json.AcpJsonMapper;
import com.agentclientprotocol.sdk.spec.AcpSchema.*;
import java.net.URI;
import java.util.List;

var transport = new StreamableHttpAcpClientTransport(
    URI.create("http://localhost:8080/acp"), AcpJsonMapper.createDefault());

AcpSyncClient client = AcpClient.sync(transport)
    .sessionUpdateHandler(notification -> {
        if (notification.update() instanceof AgentMessageChunk msg) {
            System.out.print(((TextContent) msg.content()).text());
        }
    })
    .build();

client.initialize();
var session = client.newSession(new NewSessionRequest(".", List.of()));
var response = client.prompt(new PromptRequest(
    session.sessionId(), List.of(new TextContent("Hello over HTTP!"))));

System.out.println("\nStop reason: " + response.stopReason());
client.close();
// Output: Echo: Hello over HTTP!
// Stop reason: END_TURN
```

A `WebSocketAcpClientTransport` pointed at `ws://localhost:8080/acp` talks to the same agent unchanged.

**Mounting in your own server** — `StreamableHttpAcpServlet` (module `acp-http-servlet`) serves Streamable HTTP, SSE and the WebSocket upgrade together, in any Servlet 6 container (Spring Boot, Tomcat, Jetty, Undertow) at a path you choose. Register it with async support. In Spring Boot:

```java theme={null}
import com.agentclientprotocol.sdk.agent.AcpAgentFactory;
import com.agentclientprotocol.sdk.agent.transport.StreamableHttpAcpServlet;
import com.agentclientprotocol.sdk.json.AcpJsonMapper;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
class AcpServletConfig {

    @Bean
    ServletRegistrationBean<StreamableHttpAcpServlet> acpServlet(AcpAgentFactory agents) {
        var registration = new ServletRegistrationBean<>(
            new StreamableHttpAcpServlet(AcpJsonMapper.createDefault(), agents), "/acp");
        registration.setAsyncSupported(true);
        return registration;
    }
}
```

The container's lifecycle drives the servlet: `init()` starts the SSE keep-alive and `destroy()` closes every connection, cancelling in-flight prompts. The servlet upgrades WebSocket requests itself, through the container's own Jakarta WebSocket 2.1 support, on the same path; a container with none answers the upgrade `501`.

<Note>
  The `acp-websocket-jetty` module and its single-client `WebSocketAcpAgentTransport` were deprecated for removal in 0.18.0 and are **removed in 0.80.0**. Use `StreamableHttpAcpAgentTransport` as shown above, which serves the WebSocket upgrade for any number of clients, one agent per connection; see the [0.80.0 migration guide](/docs/acp-java-sdk/migration-0.80) for the full change.
</Note>

### Release history

Version `0.18.0` adds remote agents over the Streamable HTTP and WebSocket transport and splits JSON into `acp-json-jackson2` and `acp-json-jackson3`; see [What's New](/docs/acp-java-sdk/whats-new) for the three changes to check when upgrading. Versions `0.15.0` through `0.17.0` changed no public API. Version `0.12.0` added session/list, session/close, session/resume, elicitation, session/fork, and session/set\_config\_option. Version `0.14.0` (superseding the never-released 0.13.0) adds logout, session/delete, additional workspace directories, per-chunk message IDs, and the unstable providers/\* configuration methods, promotes session/set\_config\_option to stable, and deprecates the removed session/set\_model API (use session/set\_config\_option with a `"model"` category option). Version `0.80.0` is binary-incompatible with 0.18.0 and removes `session/set_model` and `acp-websocket-jetty`; see the [0.80.0 migration guide](/docs/acp-java-sdk/migration-0.80) for every breaking change. See the [API Reference](/docs/acp-java-sdk/reference/java#installation) for installation.

## Three Agent API Styles

| Style | Entry Point | Best For |
| - | - | - |
| **Annotation-based** | `@AcpAgent`, `@Prompt` | Least boilerplate, declarative style |
| **Sync** | `AcpAgent.sync()` | Blocking handlers, plain return values |
| **Async** | `AcpAgent.async()` | Reactive applications, Project Reactor `Mono` |

All three styles produce identical protocol behavior. Choose based on programming preference.

## Documentation

<CardGroup cols={2}>
  <Card title="API Reference" icon="book" href="/docs/acp-java-sdk/reference/java">
    Client API, Agent API (all three styles), protocol types, transports, errors
  </Card>

  <Card title="Tutorial" icon="graduation-cap" href="/docs/acp-java-sdk/tutorial/index">
    32-module progressive tutorial from client basics to IDE integration
  </Card>
</CardGroup>

## Resources

* [GitHub Repository](https://github.com/agentclientprotocol/java-sdk): source code
* [ACP Java Tutorial](https://github.com/markpollack/acp-java-tutorial): 41 hands-on modules

See [ACP Resources](/docs/acp-java-sdk/resources) for the wider ecosystem: the spec site, the other
language SDKs, and editor and IDE support.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.