This directory contains both the transport-agnostic runtime that maps between ADK and the A2A specification, as well as the Spring Boot webservice and sample projects that demonstrate how to expose that runtime over HTTP.
com.google.adk.a2a.converters– pure conversion helpers between ADK events and A2A spec payloads; entirely framework- and transport-agnostic.com.google.adk.a2a.A2ASendMessageExecutorandRemoteA2AAgent– runtime entry points that orchestrate sendMessage flows using the converters; they remain service-framework agnostic but expect a transport-specific client to be supplied.com.google.adk.a2a.A2AClient– default HTTP implementation backed by the A2A SDK; swap this out when binding the runtime to another transport.a2a/webservice– Spring Boot transport module that imports the runtime library and exposes the JSON-RPC endpoint.
graph LR
classDef client fill:#E8F0FE,stroke:#1A73E8,color:#202124;
classDef runner fill:#FCE8B2,stroke:#FBBC04,color:#202124;
classDef agent fill:#FFFFFF,stroke:#5F6368,color:#202124;
subgraph ServiceA["Service A: A2A-ADK Agent"]
direction TB
L_Client["A2A-ADK Client"]
subgraph L_ADK["ADK framework"]
direction TB
L_Runner["Runner.runAsync()"]
L_Root["rootAgent"]
L_Roll["rollDieAgent"]
end
L_Client --> L_ADK
end
subgraph ServiceB["Service B: A2A-ADK Agent"]
direction TB
R_Client["A2A-ADK Client"]
subgraph R_ADK["ADK framework"]
direction TB
R_Runner["Runner.runAsync()"]
R_Check["primeCheckerAgent"]
end
R_Client --> R_ADK
end
L_Client -- "stubby request" --> R_Client
R_Client -- "stubby response" --> L_Client
class L_Client,R_Client client;
class L_Runner,R_Runner runner;
class L_Root,L_Roll,R_Check agent;
A2ASendMessageExecutorwalks the full sendMessage lifecycle: converts the inbound spec message into ADK events, ensures session state, invokes the agent (either via a provided strategy or an ownedRunner), applies timeout handling, and converts the resulting events back into a single specMessage. All logging and fallback responses live here to keep transport shells minimal.RemoteA2AAgentresolvesAgentCardmetadata (card object, URL, or file), constructs/owns anA2AClientwhen needed, builds the JSON-RPC request, and fans theSendMessageResponseback into ADK events using the converters.A2AClientis strictly an HTTP helper: it serialises the request with Jackson, posts viaA2AHttpClient, and deserialises the response. Swap it for a gRPC client without touching the rest of the package.AgentCardrefers to the spec record inio.a2a.spec; we reuse that builder for discovery metadata so clients and services share the same view.converters/*hold all mapping logic:RequestConvertergenerates ADK events from spec messages,ResponseConverterperforms the reverse and wraps results,PartConvertertranslates individual parts, andConversationPreprocessorsplits prior history versus the user turn. Nothing in here depends on any transport framework.
None of these classes depend on Spring or JSON-specific wiring (apart from the sample HTTP client), so they can be imported directly by other transports or tools.
The a2a/webservice module packages the Spring Boot transport that exposes the
REST endpoint:
A2ARemoteApplication– Spring Boot entrypoint that boots the Tomcat server and wires in the remote configuration.A2ARemoteConfiguration– Spring@Configurationthat imports the transport stack and the sharedA2ASendMessageExecutor. Provide aBaseAgentbean to handle requests locally (as thea2a_remotesample does).A2ARemoteController– JSON-RPC adapter mounted at/a2a/remote/v1.A2ARemoteService– delegates incoming requests to the executor and handles JSON-RPC error responses. All application wiring lives in this module; the core A2A logic remains in the transport-agnostica2a/src/...tree described above.
contrib/samples/a2a_basic– minimal HTTP client demo that hits a remote A2A endpoint and logs the JSON-RPC exchange. Useful for verifying the transport without standing up the full Spring service.contrib/samples/a2a_remote– standalone Spring service mirroring the Stubby demo. It depends on the shareda2a/webservicemodule so the sample only provides the prime-agent wiring while reusing the production controller and service stack.
All commands below assume you are in google_adk.
-
Start the Spring webservice sample (run in its own terminal)
lsof -ti :8081 | xargs -r kill ./mvnw -f contrib/samples/a2a_remote/pom.xml spring-boot:run \ -Dspring-boot.run.arguments=--server.port=8081
Background option:
nohup env GOOGLE_GENAI_USE_VERTEXAI=FALSE \ GOOGLE_API_KEY=your_api_key \ ./mvnw -f contrib/samples/a2a_remote/pom.xml spring-boot:run \ -Dspring-boot.run.arguments=--server.port=8081 \ > /tmp/a2a_webservice.log 2>&1 & echo $!
The log can be found at /tmp/a2a_webservice.log.
-
Run the basic client sample (
a2a_basic) (from another terminal)GOOGLE_GENAI_USE_VERTEXAI=FALSE \ GOOGLE_API_KEY=your_api_key \ ./mvnw -f contrib/samples/a2a_basic/pom.xml exec:java \ -Dexec.args="http://localhost:8081/a2a/remote"The client logs the outbound JSON-RPC payload and shows the remote agent’s reply (for example,
4 is not a prime number.).The first run downloads dependencies from Maven Central. Configure a mirror in
~/.m2/settings.xmlif your environment restricts outbound traffic.Background option:
nohup env GOOGLE_GENAI_USE_VERTEXAI=FALSE \ GOOGLE_API_KEY=your_api_key \ ./mvnw -f contrib/samples/a2a_basic/pom.xml exec:java \ -Dexec.args="http://localhost:8081/a2a/remote" \ > /tmp/a2a_basic.log 2>&1 & echo $!
Tail
/tmp/a2a_basic.logto observe subsequent turns.
To build the runtime, Spring webservice, and both samples together, activate the opt-in Maven profile:
./mvnw -pl a2a -am clean packageWith the server running locally you can exercise the endpoint with curl:
curl -X POST http://localhost:8081/a2a/remote/v1/message:send \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "cli-check-2",
"method": "message/send",
"params": {
"message": {
"kind": "message",
"contextId": "cli-demo-context",
"messageId": "cli-check-2",
"role": "user",
"parts": [
{ "kind": "text", "text": "Is 2 prime?" }
]
}
}
}'Use a fresh id/messageId for each attempt if you want the request/response
pair logged distinctly. Dropping the contextId is fine—the service will
generate a UUID and return it in the response.
Sample response:
{
"jsonrpc": "2.0",
"id": "cli-check-6",
"result": {
"role": "agent",
"parts": [
{
"data": {
"args": { "nums": [6] },
"name": "checkPrime"
},
"metadata": { "type": "function_call" },
"kind": "data"
},
{
"data": {
"response": { "result": "No prime numbers found." },
"name": "checkPrime"
},
"metadata": { "type": "function_response" },
"kind": "data"
},
{
"text": "No prime numbers found.",
"kind": "text"
}
],
"messageId": "36b2a2a4-87c7-4800-b7f7-8e7c73d2f25e",
"contextId": "cli-demo-context",
"kind": "message"
}
}