Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn more
The Agent2Agent (A2A) Protocol is an open standard for seamless AI agent communication. It enables agents to discover capabilities, exchange messages, and coordinate workflows across platforms—regardless of their implementation.
Spring AI A2A integrates the A2A Java SDK with Spring AI through Spring Boot autoconfiguration. It seamlessly connects the A2A protocol with Spring AI's ChatClient and tools, enabling you to expose your agents as A2A servers.
This post is part of the Spring AI Agentic Patterns series. While previous posts covered making individual agents more capable (Agent Skills, AskUserQuestionTool, TodoWriteTool, Sub-agent orchestration), this post shows how the A2A Protocol enables agents to collaborate across system boundaries through practical examples.
The A2A Protocol is an open standard for AI agent communication, providing a vendor-neutral foundation that enables agents to discover capabilities, exchange messages, and coordinate workflows. Built on HTTP, SSE, and JSON-RPC standards.
Agent Discovery is the foundation of A2A communication. Agents discover each other's capabilities through the AgentCard—a standardized JSON document exposed at /.well-known/agent-card.json that describes an agent's identity, capabilities, and skills. This follows a three-step pattern: Discovery → Initiation → Completion.
The protocol defines two roles: A2A Server agents expose endpoints for discovery and message handling, while A2A Client agents initiate communication by discovering remote agents and sending messages.
The A2A Java SDK provides a Java implementation with server-side components for processing requests and managing tasks, and client-side components for calling remote agents. It supports multiple transports (HTTP, SSE, JSON-RPC) and handles low-level protocol details.
While the A2A Java SDK provides the protocol implementation, integrating it with Spring AI requires additional plumbing. This is where the Spring AI A2A project comes in.
The spring-ai-a2a project currently focuses on the server-side integration, enabling you to expose your Spring AI agents as A2A-compliant servers.
This covers:
ChatClient and toolsDefaultAgentExecutor that bridges A2A SDK and Spring AIHere's what the integration handles for you:
// The framework automatically exposes these A2A endpoints (relative to your context path):
POST / // Handle JSON-RPC sendMessage requests
GET /.well-known/agent-card.json // Agent card (standard A2A location)
GET /card // Agent card (alternative endpoint)
Step-by-step:
AgentCard from /.well-known/agent-card.json to discover capabilities, skills, and protocol detailsMessageController receives the A2A JSON-RPC sendMessage request at the root endpointAgentExecutor bean (Spring AI A2A provides the default implementation: DefaultAgentExecutor)ChatClientExecutorHandler lambda is invoked with the Spring AI ChatClient and request contextChatClient response is packaged as an A2A JSON-RPC messageLet's build interoperable agent systems with Spring AI A2A. We'll start with prerequisites and setup, then work through practical examples: a single-agent server and a multi-agent orchestration.
Add the Spring AI A2A starter to your project to expose your Spring AI agent as an A2A server:
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-a2a-server-autoconfigure</artifactId>
<version>0.2.0</version>
</dependency>
This starter includes the A2A Java SDK (v0.3.3.Final) as a transitive dependency.
For applications that need to call remote A2A agents, explicitly add the A2A SDK client:
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-client</artifactId>
<version>0.3.3.Final</version>
</dependency>
Configure your application in application.properties:
# Server configuration
server.servlet.context-path=/weather
# LLM Configuration (example with Anthropic Claude)
spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
spring.ai.anthropic.chat.options.model=claude-sonnet-4-5-20250929
# For multi-agent orchestration (Example 2), configure remote agent URLs:
# remote.agents.urls=http://localhost:10001/foo/,http://localhost:10002/bar/
Expose a Spring AI application with tools as an A2A server:
@Configuration
public class WeatherAgentConfiguration {
@Bean
public AgentCard agentCard(@Value("${server.port:8080}") int port,
@Value("${server.servlet.context-path:/}") String contextPath) {
// This AgentCard is automatically exposed at /.well-known/agent-card.json
// Other agents discover this agent's capabilities through this endpoint
return new AgentCard.Builder()
.name("Weather Agent")
.description("Provides weather information for cities")
.url("http://localhost:" + port + contextPath + "/")
.version("1.0.0")
.capabilities(new AgentCapabilities.Builder().streaming(false).build())
.defaultInputModes(List.of("text"))
.defaultOutputModes(List.of("text"))
.skills(List.of(new AgentSkill.Builder()
.id("weather_search")
.name("Search weather")
.description("Get temperature for any city")
.tags(List.of("weather"))
.examples(List.of("What's the weather in London?"))
.build()))
.protocolVersion("0.3.0")
.build();
}
@Bean
public AgentExecutor agentExecutor(ChatClient.Builder chatClientBuilder, WeatherTools weatherTools) {
ChatClient chatClient = chatClientBuilder.clone()
.defaultSystem("You are a weather assistant. Use the temperature tool to answer questions.")
.defaultTools(weatherTools) // Register Spring AI tools
.build();
return new DefaultAgentExecutor(chatClient, (chat, requestContext) -> {
String userMessage = DefaultAgentExecutor.extractTextFromMessage(requestContext.getMessage());
return chat.prompt().user(userMessage).call().content();
});
}
}
@Service
class WeatherTools {
...
}
Your Spring AI agent is now an A2A-compliant server. Other agents can discover its capabilities through the standard /.well-known/agent-card.json endpoint and then send weather queries via POST to the root endpoint. The autoconfiguration handles exposing both the AgentCard and message endpoints.
Here's a practical example of an agent client: A host agent that orchestrates specialized agents for travel planning (Airbnb accommodations and weather information).
@Service
public class RemoteAgentConnections {
private final Map<String, AgentCard> agentCards = new HashMap<>();
public RemoteAgentConnections(@Value("${remote.agents.urls}") List<String> agentUrls) {
// Discover remote agents at startup (see Agent Discovery section above)
for (String url : agentUrls) {
String path = new URI(url).getPath();
AgentCard card = A2A.getAgentCard(url, path + ".well-known/agent-card.json", null);
this.agentCards.put(card.name(), card);
}
}
@Tool(description = "Sends a task to a remote agent. Use this to delegate work to specialized agents.")
public String sendMessage(
@ToolParam(description = "The name of the agent") String agentName,
@ToolParam(description = "The task description to send") String task) {
AgentCard agentCard = this.agentCards.get(agentName);
// Create A2A message
Message message = new Message.Builder()
.role(Message.Role.USER)
.parts(List.of(new TextPart(task, null)))
.build();
// Use A2A Java SDK Client
CompletableFuture<String> responseFuture = new CompletableFuture<>();
Client client = Client.builder(agentCard)
.clientConfig(new ClientConfig.Builder()
.setAcceptedOutputModes(List.of("text"))
.build())
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig())
.addConsumers(List.of(consumer -> {
if (consumer instanceof TextPart textPart) {
responseFuture.complete(textPart.getText());
}
}))
.build();
client.sendMessage(message);
return responseFuture.get(60, TimeUnit.SECONDS);
}
public String getAgentDescriptions() {
return agentCards.values().stream()
.map(card -> card.name() + ": " + card.description())
.collect(Collectors.joining("\n"));
}
}
@Configuration
public class HostAgentConfiguration {
@Bean
public ChatClient routingChatClient(ChatClient.Builder chatClientBuilder,
RemoteAgentConnections remoteAgentConnections) {
String systemPrompt = """
You coordinate tasks across specialized agents.
Available agents:
%s
Use the sendMessage tool to delegate tasks to the appropriate agent.
""".formatted(remoteAgentConnections.getAgentDescriptions());
return chatClientBuilder
.defaultSystem(systemPrompt)
.defaultTools(remoteAgentConnections) // Register as Spring AI tool
.build();
}
}
What's happening here:
AgentCard from the standard .well-known/agent-card.json endpointsRemoteAgentConnections is registered as a Spring AI @Tool with the ChatClientsendMessage toolThis pattern enables LLM-driven routing—the model decides which specialized agents to invoke based on the user's query.
While the current release focuses on server-side integration, the Spring AI community is exploring opportunities to enhance the A2A client experience for Spring AI applications.
Potential Future Enhancements:
These enhancements would provide additional support for building Spring AI applications that act as A2A agent clients, providing consistent integration patterns for both client and server implementations.
Want to contribute? The Spring AI A2A project welcomes community contributions. Check out the GitHub repository to get involved.
The A2A Protocol represents a significant step toward interoperable AI agent ecosystems. By standardizing how agents communicate, it removes barriers to building sophisticated multi-agent systems.
The Spring AI A2A community project provides the integration needed to participate in this ecosystem. Through Spring Boot autoconfiguration, you can expose your Spring AI agents as A2A servers, integrate with other A2A-compliant agents, and build orchestration patterns that leverage Spring Boot's conventions.
As the A2A ecosystem grows, more agents, tools, and platforms may adopt this standard, expanding options for agent collaboration and composition.
To begin integrating A2A Protocol support with your Spring AI agents, refer to the resources below.