Skip to content

Commit 5ce8674

Browse files
fjumacescoffier
authored andcommitted
Add a blog post about getting started with A2A Java SDK and gRPC
1 parent 2187e1f commit 5ce8674

File tree

1 file changed

+347
-0
lines changed

1 file changed

+347
-0
lines changed
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
---
2+
layout: post
3+
title: 'Getting Started with A2A Java SDK and gRPC'
4+
date: 2025-09-25
5+
tags: ai a2a grpc
6+
synopsis: Let's learn how to create an A2A server agent and an A2A client that can communicate using the gRPC transport.
7+
author: fjuma
8+
---
9+
10+
The ability for AI agents to communicate across different frameworks and languages is key to
11+
building polyglot multi-agent systems. The recent https://quarkus.io/blog/quarkus-a2a-java-0-3-0-alpha-release/[0.3.0.Alpha1] and https://quarkus.io/blog/quarkus-a2a-java-0-3-0-beta-release/[0.3.0.Beta1] releases of the A2A Java SDK take a
12+
significant step forward in this area by adding support for the gRPC transport and the HTTP+JSON/REST transport, offering greater flexibility and improved performance.
13+
14+
In this post, we'll demonstrate how to create an A2A server agent and an A2A client that support
15+
multiple transports, where the gRPC transport will be selected.
16+
17+
== Dice Agent Sample
18+
19+
To see the multi-transport support in action, we're going to take a look at the new
20+
https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/dice_agent_multi_transport[Dice Agent]
21+
sample from the https://github.com/a2aproject/a2a-samples[a2a-samples] repo.
22+
23+
The `DiceAgent` is a simple https://github.com/a2aproject/a2a-samples/blob/main/samples/java/agents/dice_agent_multi_transport/server/src/main/java/com/samples/a2a/DiceAgent.java[Quarkus LangChain4j AI service] that can make use of https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/dice_agent_multi_transport/server/src/main/java/com/samples/a2a/DiceTools.java[tools] to roll dice of different sizes and check if the result of a roll is a prime number.
24+
25+
=== A2A Server Agent
26+
27+
There are three key things in our sample application that turn our Quarkus LangChain4j AI service into an A2A
28+
server agent:
29+
30+
1. A dependency on at least one A2A Java SDK Server Reference implementation in the server application's
31+
https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/dice_agent_multi_transport/server/pom.xml[pom.xml] file. In this sample, we've added dependencies on both `io.github.a2asdk:a2a-java-sdk-reference-grpc`
32+
and `io.github.a2asdk:a2a-java-sdk-reference-jsonrpc` since we want our A2A server agent to be able to support
33+
both the gRPC and JSON-RPC transports.
34+
2. The https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/dice_agent_multi_transport/server/src/main/java/com/samples/a2a/DiceAgentCardProducer.java[DiceAgentCardProducer], which defines the `AgentCard` for our A2A server agent.
35+
3. The https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/dice_agent_multi_transport/server/src/main/java/com/samples/a2a/DiceAgentExecutorProducer.java[DiceAgentExecutorProducer], which calls our `DiceAgent` AI service.
36+
37+
Let's look closer at the `DiceAgentCardProducer`:
38+
39+
[source,java]
40+
----
41+
/**
42+
* Producer for dice agent card configuration.
43+
*/
44+
@ApplicationScoped
45+
public final class DiceAgentCardProducer {
46+
47+
/** The HTTP port for the agent service. */
48+
@Inject
49+
@ConfigProperty(name = "quarkus.http.port")
50+
private int httpPort;
51+
52+
/**
53+
* Produces the agent card for the dice agent.
54+
*
55+
* @return the configured agent card
56+
*/
57+
@Produces
58+
@PublicAgentCard
59+
public AgentCard agentCard() {
60+
return new AgentCard.Builder()
61+
.name("Dice Agent")
62+
.description(
63+
"Rolls an N-sided dice and answers questions about the "
64+
+ "outcome of the dice rolls. Can also answer questions "
65+
+ "about prime numbers.")
66+
.preferredTransport(TransportProtocol.GRPC.asString()) <1>
67+
.url("localhost:" + httpPort) <2>
68+
.version("1.0.0")
69+
.documentationUrl("http://example.com/docs")
70+
.capabilities(
71+
new AgentCapabilities.Builder()
72+
.streaming(true)
73+
.pushNotifications(false)
74+
.stateTransitionHistory(false)
75+
.build())
76+
.defaultInputModes(List.of("text"))
77+
.defaultOutputModes(List.of("text"))
78+
.skills(
79+
List.of(
80+
new AgentSkill.Builder()
81+
.id("dice_roller")
82+
.name("Roll dice")
83+
.description("Rolls dice and discusses outcomes")
84+
.tags(List.of("dice", "games", "random"))
85+
.examples(List.of("Can you roll a 6-sided die?"))
86+
.build(),
87+
new AgentSkill.Builder()
88+
.id("prime_checker")
89+
.name("Check prime numbers")
90+
.description("Checks if given numbers are prime")
91+
.tags(List.of("math", "prime", "numbers"))
92+
.examples(
93+
List.of("Is 17 a prime number?"))
94+
.build()))
95+
.protocolVersion("0.3.0")
96+
.additionalInterfaces( <3>
97+
List.of(
98+
new AgentInterface(TransportProtocol.GRPC.asString(), <4>
99+
"localhost:" + httpPort),
100+
new AgentInterface(
101+
TransportProtocol.JSONRPC.asString(), <5>
102+
"http://localhost:" + httpPort)))
103+
.build();
104+
}
105+
}
106+
107+
----
108+
<1> The preferred transport for our A2A server agent, `gRPC` in this sample. This is the transport protocol available at the primary endpoint URL.
109+
<2> This is the primary endpoint URL for our A2A server agent. Since `gRPC` is our preferred transport and since
110+
we'll be using the HTTP port for gRPC and JSON-RPC, we're specifying `"localhost:" + httpPort` here.
111+
<3> We can optionally specify additional interfaces supported by our A2A server agent here. Since we also want
112+
to support the JSON-RPC transport, we'll be adding that in this section.
113+
<4> The primary endpoint URL can optionally be specified in the additional interfaces section for completeness.
114+
<5> The JSON-RPC transport URL. Notice that we're using the HTTP port for both JSON-RPC and gRPC.
115+
116+
==== Port Configuration for the Transports
117+
118+
In the previous section, we mentioned that we're using the HTTP port for both the gRPC and JSON-RPC transports.
119+
This is configured in our `application.properties` file as shown here:
120+
121+
[source,properties]
122+
----
123+
# Use the same port for gRPC and HTTP
124+
quarkus.grpc.server.use-separate-server=false
125+
quarkus.http.port=11000
126+
----
127+
128+
This setting allows serving both plain HTTP and gRPC requests from the same HTTP server. Underneath it uses a Vert.x based gRPC server. If you set this setting to true, gRPC requests will be served on port 9000 (and gRPC Java will be used instead).
129+
130+
==== Starting the A2A Server Agent
131+
132+
Once we start our Quarkus application, our A2A server agent will be available at localhost:11000 for clients that would like to use gRPC and at http://localhost:11000 for clients that would like to use JSON-RPC.
133+
134+
A2A clients can now send queries to our A2A server agent using either the gRPC or JSON-RPC transport.
135+
136+
The complete source code and instructions for starting the server application are available https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/dice_agent_multi_transport/server[here].
137+
138+
Now that we have our multi-transport server agent configured and ready to go, let's take a look at how to create an A2A client that can communicate with it.
139+
140+
== A2A Client
141+
142+
The `dice_agent_multi_transport` sample also includes a https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/dice_agent_multi_transport/client/src/main/java/com/samples/a2a/client/TestClient.java[TestClient] that can be used to send messages to the Dice Agent.
143+
144+
Notice that the client's https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/dice_agent_multi_transport/client/pom.xml[pom.xml] file contains dependencies on `io.github.a2asdk:a2a-java-sdk-client` and `io.github.a2asdk:a2a-java-sdk-client-transport-grpc`.
145+
146+
The `a2a-java-sdk-client` dependency provides access to a `Client.builder` that we'll use to create our A2A client and also provides the ability for the client to support the JSON-RPC transport.
147+
148+
The `a2a-java-sdk-client-transport-grpc` dependency provides the ability for the client to support the gRPC transport.
149+
150+
Let's see how the `TestClient` uses the A2A Java SDK to create a `Client` that supports both gRPC and JSON-RPC:
151+
152+
[source,java]
153+
----
154+
...
155+
// Fetch the public agent card
156+
AgentCard publicAgentCard = new A2ACardResolver(serverUrl).getAgentCard();
157+
158+
// Create a CompletableFuture to handle async response
159+
final CompletableFuture<String> messageResponse = new CompletableFuture<>();
160+
161+
// Create consumers for handling client events
162+
List<BiConsumer<ClientEvent, AgentCard>> consumers = getConsumers(messageResponse);
163+
164+
// Create error handler for streaming errors
165+
Consumer<Throwable> streamingErrorHandler = (error) -> {
166+
System.out.println("Streaming error occurred: " + error.getMessage());
167+
error.printStackTrace();
168+
messageResponse.completeExceptionally(error);
169+
};
170+
171+
// Create channel factory for gRPC transport
172+
Function<String, Channel> channelFactory = agentUrl -> {
173+
return ManagedChannelBuilder.forTarget(agentUrl).usePlaintext().build();
174+
};
175+
176+
ClientConfig clientConfig = new ClientConfig.Builder()
177+
.setAcceptedOutputModes(List.of("Text"))
178+
.build();
179+
180+
// Create the client with both JSON-RPC and gRPC transport support.
181+
// The A2A server agent's preferred transport is gRPC, since the client
182+
// also supports gRPC, this is the transport that will get used
183+
Client client = Client.builder(publicAgentCard) <1>
184+
.addConsumers(consumers) <2>
185+
.streamingErrorHandler(streamingErrorHandler) <3>
186+
.withTransport(GrpcTransport.class, new GrpcTransportConfig(channelFactory)) <4>
187+
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig()) <5>
188+
.clientConfig(clientConfig) <6>
189+
.build();
190+
191+
// Create and send the message
192+
Message message = A2A.toUserMessage(messageText);
193+
194+
System.out.println("Sending message: " + messageText);
195+
client.sendMessage(message); <7>
196+
System.out.println("Message sent successfully. Waiting for response...");
197+
198+
try {
199+
// Wait for response with timeout
200+
String responseText = messageResponse.get();
201+
System.out.println("Final response: " + responseText);
202+
} catch (Exception e) {
203+
System.err.println("Failed to get response: " + e.getMessage());
204+
e.printStackTrace();
205+
}
206+
...
207+
----
208+
<1> We can use `Client.builder(publicAgentCard)` to create our A2A client. We need to pass in the `AgentCard` retrieved from the A2A server agent this client will be communicating with.
209+
<2> We need to specify event consumers that will be used to handle the responses that will be received from the
210+
A2A server agent. This will be explained in more detail in the next section.
211+
<3> The A2A client created by the `Client.builder` will automatically send streaming messages, as opposed to
212+
non-streaming messages, if it's supported by both the server and the client. We need to specify a handler that will be used for any errors that might occur during streaming.
213+
<4> We're indicating that we'd like our client to support the gRPC transport.
214+
<5> We're indicating that we'd like our client to also support the JSON-RPC transport. When communicating with
215+
an A2A server agent that doesn't support gRPC, this is the transport that would get used.
216+
<6> We can optionally specify general client configuration and preferences here.
217+
<7> Once our `Client` has been created, we can send a message to the A2A server agent. The client will automatically use streaming if it's supported by both the server and the client. If the server doesn't
218+
support streaming, the client will send a non-streaming message instead.
219+
220+
=== Defining the Event Consumers
221+
222+
When creating our A2A client, we need to specify event consumers that will be used to handle the responses
223+
that will be received from the A2A server agent. Let's see how to define a consumer that handles the different
224+
types of events:
225+
226+
[source,java]
227+
----
228+
private static List<BiConsumer<ClientEvent, AgentCard>> getConsumers(
229+
final CompletableFuture<String> messageResponse) {
230+
List<BiConsumer<ClientEvent, AgentCard>> consumers = new ArrayList<>();
231+
consumers.add(
232+
(event, agentCard) -> {
233+
if (event instanceof MessageEvent messageEvent) { <1>
234+
Message responseMessage = messageEvent.getMessage();
235+
String text = extractTextFromParts(responseMessage.getParts());
236+
System.out.println("Received message: " + text);
237+
messageResponse.complete(text);
238+
} else if (event instanceof TaskUpdateEvent taskUpdateEvent) { <2>
239+
UpdateEvent updateEvent = taskUpdateEvent.getUpdateEvent();
240+
if (updateEvent
241+
instanceof TaskStatusUpdateEvent taskStatusUpdateEvent) { <3>
242+
System.out.println("Received status-update: "
243+
+ taskStatusUpdateEvent.getStatus().state().asString());
244+
if (taskStatusUpdateEvent.isFinal()) {
245+
StringBuilder textBuilder = new StringBuilder();
246+
List<Artifact> artifacts
247+
= taskUpdateEvent.getTask().getArtifacts();
248+
for (Artifact artifact : artifacts) {
249+
textBuilder.append(extractTextFromParts(artifact.parts()));
250+
}
251+
String text = textBuilder.toString();
252+
messageResponse.complete(text);
253+
}
254+
} else if (updateEvent
255+
instanceof TaskArtifactUpdateEvent taskArtifactUpdateEvent) { <4>
256+
List<Part<?>> parts = taskArtifactUpdateEvent
257+
.getArtifact()
258+
.parts();
259+
String text = extractTextFromParts(parts);
260+
System.out.println("Received artifact-update: " + text);
261+
}
262+
} else if (event instanceof TaskEvent taskEvent) { <5>
263+
System.out.println("Received task event: "
264+
+ taskEvent.getTask().getId());
265+
}
266+
});
267+
return consumers;
268+
}
269+
----
270+
<1> This defines how to handle a `Message` received from the server agent. The server agent will send a response that contains a `Message` for immediate, self-contained interactions that are stateless.
271+
<2> This defines how to handle an `UpdateEvent` received from the server agent for a specific task. There are
272+
two types of `UpdateEvents` that can be received.
273+
<3> A `TaskStatusUpdateEvent` notifies the client of a change in a task's status. This is typically used in streaming interactions. If this is the final event in the stream for this interaction, `taskStatusUpdateEvent.isFinal()`
274+
will return `true`.
275+
<4> A `TaskArtifactUpdateEvent` notifies the client that an artifact has been generated or updated. An artifact contains output generated by an agent during a task. This is typically used in streaming interactions.
276+
<5> This defines how to handle a `Task` received from the server agent. A `Task` will be processed by the server agent through a defined lifecycle until it reaches an interrupted state or a terminal state.
277+
278+
=== Transport Selection
279+
280+
When creating our `Client`, we used the `withTransport` method to specify that we want the client
281+
to support both gRPC and JSON-RPC, in that order. The `Client.builder` selects the appropriate
282+
transport protocol to use based on information obtained from the A2A server agent's `AgentCard`,
283+
taking into account the transports configured for the client. In this sample application, because
284+
the server agent's preferred transport is gRPC, the gRPC transport will be used.
285+
286+
=== Using the A2A Client
287+
288+
The sample application contains a `TestClientRunner` that can be run using `JBang`:
289+
290+
[source,shell]
291+
----
292+
jbang TestClientRunner.java
293+
----
294+
295+
You should see output similar to this:
296+
297+
[source,shell]
298+
----
299+
Connecting to dice agent at: http://localhost:11000
300+
Successfully fetched public agent card:
301+
...
302+
Sending message: Can you roll a 5 sided die?
303+
Message sent successfully. Waiting for response...
304+
Received status-update: submitted
305+
Received status-update: working
306+
Received artifact-update: Sure! I rolled a 5 sided die and got a 3.
307+
Received status-update: completed
308+
Final response: Sure! I rolled a 5 sided die and got a 3.
309+
----
310+
311+
You can also experiment with sending different messages to the A2A server agent using the `--message` option
312+
as follows:
313+
314+
[source,shell]
315+
----
316+
jbang TestClientRunner.java --message "Can you roll a 13-sided die and check if the result is a prime number?"
317+
Connecting to dice agent at: http://localhost:11000
318+
Successfully fetched public agent card:
319+
...
320+
Sending message: Can you roll a 13-sided die and check if the result is a prime number?
321+
Message sent successfully. Waiting for response...
322+
Received status-update: submitted
323+
Received status-update: working
324+
Received artifact-update: I rolled a 13 sided die and got a 3. 3 is a prime number.
325+
Received status-update: completed
326+
Final response: I rolled a 13 sided die and got a 3. 3 is a prime number.
327+
----
328+
329+
The complete source code and instructions for starting the client are available https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/dice_agent_multi_transport/client[here]. There
330+
are also details on how to use an A2A client that uses the A2A Python SDK instead of the A2A Java SDK
331+
to communicate with our A2A server agent.
332+
333+
== Conclusion
334+
335+
The addition of multi-transport support to the A2A Java SDK, as demonstrated in the new Dice Agent
336+
sample, is a big step towards creating more flexible, performant, polyglot multi-agent systems.
337+
338+
=== Further Reading
339+
340+
* https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/dice_agent_multi_transport[Dice Agent Sample]
341+
* https://quarkus.io/blog/quarkus-a2a-java-0-3-0-alpha-release/[Getting Started with Quarkus and A2A Java SDK 0.3.0]
342+
* https://quarkus.io/blog/quarkus-a2a-java-0-3-0-beta-release/[A2A Java SDK: Support for the REST Transport is Now Here]
343+
* https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents[A2A Java SDK Samples]
344+
* https://github.com/a2aproject/a2a-java/blob/main/README.md[A2A Java SDK Documentation]
345+
* https://a2a-protocol.org/latest/specification/[A2A Specification]
346+
347+

0 commit comments

Comments
 (0)