Spring Boot integration for building AI agent servers using the A2A Protocol.
Spring AI A2A provides server-side support for exposing Spring AI agents via the A2A protocol. Add the dependency, provide ChatClient, AgentCard, and AgentExecutor beans, and your agent is automatically exposed.
Key Features: A2A Protocol Server • Spring AI ChatClient Integration • Full @Tool Support • Auto-Configuration
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-a2a-server-autoconfigure</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>@SpringBootApplication
public class WeatherAgentApplication {
public static void main(String[] args) {
SpringApplication.run(WeatherAgentApplication.class, args);
}
@Bean
public AgentCard agentCard(@Value("${server.port:8080}") int port) {
return new AgentCard.Builder()
.name("Weather Agent")
.description("Provides weather forecasts")
.url("http://localhost:" + port + "/a2a/")
.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("Weather Search")
.description("Provides current weather and forecasts")
.tags(List.of("weather")).build()))
.protocolVersion("0.3.0")
.build();
}
@Bean
public AgentExecutor agentExecutor(ChatClient.Builder chatClientBuilder, WeatherTools tools) {
ChatClient chatClient = chatClientBuilder.clone()
.defaultSystem("You are a weather assistant. Use tools to get weather data.")
.defaultTools(tools)
.build();
return new DefaultAgentExecutor(chatClient, (chat, ctx) -> {
String userMessage = DefaultAgentExecutor.extractTextFromMessage(ctx.getMessage());
return chat.prompt().user(userMessage).call().content();
});
}
}@Service
public class WeatherTools {
@Tool(description = "Get current weather for a location")
public String getCurrentWeather(
@ToolParam(description = "City and state, e.g. San Francisco, CA") String location) {
return "Current weather in " + location + ": Sunny, 72°F";
}
}server:
port: 8080
servlet:
context-path: /a2a
spring:
ai:
a2a.server.enabled: true
openai.api-key: ${OPENAI_API_KEY}export OPENAI_API_KEY=your-key && mvn spring-boot:runYour agent is available at http://localhost:8080/a2a (card at /a2a/card).
Using curl:
curl -X POST http://localhost:8080/a2a -H 'Content-Type: application/json' -d '{
"jsonrpc": "2.0", "method": "sendMessage", "id": "1",
"params": {"message": {"role": "user", "parts": [{"type": "text", "text": "Weather in London?"}]}}
}'Using A2A SDK:
A2ACardResolver resolver = new A2ACardResolver(new JdkA2AHttpClient(), "http://localhost:8080", "/a2a/card", null);
Client client = Client.builder(resolver.getAgentCard())
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig()).build();
client.sendMessage(new Message.Builder().role(Message.Role.USER)
.parts(List.of(new TextPart("Weather in Paris?"))).build());Your Spring Boot App
├── ChatClient, AgentCard, AgentExecutor (your beans)
├── DefaultAgentExecutor (bridges A2A ↔ ChatClient)
└── A2A Controllers (auto-configured)
├── POST / → MessageController (sendMessage)
├── GET /card → AgentCardController (discovery)
└── GET /tasks/{id} → TaskController (status)
Request Flow:
MessageControllerreceives JSON-RPC request- A2A SDK
RequestHandlercreates task DefaultAgentExecutorinvokes yourChatClientExecutorHandlerChatClientcalls LLM and executes tools- Response wrapped as task artifact → returned to client
You provide: AgentExecutor, AgentCard, ChatClient beans
Framework handles: Endpoints, task lifecycle, JSON-RPC, error handling
Use DefaultAgentExecutor with a lambda:
@Bean
public AgentExecutor agentExecutor(ChatClient.Builder builder, MyTools tools) {
ChatClient chatClient = builder.clone().defaultSystem("...").defaultTools(tools).build();
return new DefaultAgentExecutor(chatClient, (chat, ctx) -> {
String msg = DefaultAgentExecutor.extractTextFromMessage(ctx.getMessage());
return chat.prompt().user(msg).call().content();
});
}For custom task lifecycle or progress updates:
@Component
public class MyAgent implements AgentExecutor {
@Override
public void execute(RequestContext ctx, EventQueue queue) throws JSONRPCError {
TaskUpdater updater = new TaskUpdater(ctx, queue);
if (ctx.getTask() == null) updater.submit();
updater.startWork();
updater.addMessage(List.of(new TextPart("Processing...")), "assistant");
String response = chatClient.prompt().user(extractText(ctx.getMessage())).call().content();
updater.addArtifact(List.of(new TextPart(response)), null, null, null);
updater.complete();
}
}Create tools that call remote A2A agents:
@Service
public class RemoteAgentTools {
private final Map<String, AgentCard> agents;
@Tool(description = "Delegate task to remote agent")
public String sendMessage(@ToolParam(description = "Agent name") String name,
@ToolParam(description = "Task") String task) {
CompletableFuture<String> future = new CompletableFuture<>();
Client client = Client.builder(agents.get(name))
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig())
.addConsumers(List.of((event, card) -> {
if (event instanceof TaskEvent te && te.getTask().getArtifacts() != null)
future.complete(extractText(te.getTask().getArtifacts()));
})).build();
client.sendMessage(new Message.Builder().role(Message.Role.USER)
.parts(List.of(new TextPart(task))).build());
return future.get(60, TimeUnit.SECONDS);
}
}See airbnb-planner example for complete multi-agent implementation.
spring-ai-a2a/
├── spring-ai-a2a-server/ # Core: executor/, controller/
├── spring-ai-a2a-server-autoconfigure/ # Auto-configuration
└── spring-ai-a2a-examples/airbnb-planner/
├── weather-agent/ (Port 10001)
├── airbnb-agent/ (Port 10002)
└── host-agent/ (Port 10000) - orchestrator
Spring Boot (application.properties or application.yml):
spring:
ai:
a2a:
server:
enabled: true
server:
servlet:
context-path: /a2aA2A SDK properties can be configured directly in Spring's Environment (application.properties, environment variables, etc.):
# Blocking call timeouts
a2a.blocking.agent.timeout.seconds=30
a2a.blocking.consumption.timeout.seconds=5
# Thread pool configuration
a2a.executor.core-pool-size=5
a2a.executor.max-pool-size=50
a2a.executor.keep-alive-seconds=60The SpringA2AConfigProvider first checks Spring's Environment for property values and falls back to the SDK's DefaultValuesConfigProvider for any missing keys. This follows the custom config provider pattern recommended by the A2A Java SDK.
mvn clean install # Build all
mvn test # Run tests
cd spring-ai-a2a-examples/airbnb-planner/weather-agent && mvn spring-boot:runJava 17+ • Maven 3.8+ • Spring Boot 4.0+ • Spring AI 2.0.0-M2+ • A2A SDK 0.3.3.Final
| Issue | Solution |
|---|---|
| Requests timeout | Check AgentExecutor bean, API key, increase timeout |
| 404 on endpoint | Verify spring.ai.a2a.server.enabled=true and context-path |
| Tools not called | Ensure tools are @Service/@Component and registered via .defaultTools() |
Apache License 2.0