Skip to content

Commit 6f28218

Browse files
committed
Add a blog post about how to get started with Quarkus and A2A
1 parent 72c238e commit 6f28218

File tree

6 files changed

+288
-0
lines changed

6 files changed

+288
-0
lines changed

_data/authors.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,3 +619,10 @@ maeste:
619619
job_title: "Senior Engineering Manager"
620620
twitter: "maeste"
621621
bio: "Senior Engineering Manager at Red Hat / IBM with an old passion for Open Source and more recent one for AI Engineering."
622+
fjuma:
623+
name: "Farah Juma"
624+
625+
emailhash: "e07a676199f6ad0af96a61a691b114b3"
626+
job_title: "Principal Software Engineer"
627+
twitter: "farahjuma"
628+
bio: "Principal Software Engineer at Red Hat / IBM working on AI projects"
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
---
2+
layout: post
3+
title: 'Getting Started with Quarkus and the A2A Java SDK'
4+
date: 2025-07-01
5+
tags: release
6+
synopsis: 'Today, we released A2A Java SDK 0.2.3. This makes it possible to quickly get started with Quarkus and A2A.'
7+
author: fjuma
8+
---
9+
:imagesdir: /assets/images/posts/quarkus-and-a2a-java-sdk
10+
11+
Last week, we https://quarkus.io/blog/a2a-project-launches-java-sdk/[announced] that our https://github.com/a2aproject/a2a-java[A2A Java SDK] has been contributed to the official A2A project! This was a collaboration between our WildFly and Quarkus teams at Red Hat and Google. Today, we have released A2A Java SDK 0.2.3, which aligns with the v0.2.3 version of the https://github.com/a2aproject/A2A/tree/v0.2.3[A2A specification]. In this blog post, we'll cover how to easily get started with Quarkus and A2A using the A2A Java SDK.
12+
13+
== What's A2A?
14+
15+
Before jumping into the details, let's go through what https://a2aproject.github.io/A2A/specification/[A2A] is. The Agent2Agent or A2A protocol for short, is an open standard that was created by Google. It allows AI agents to communicate and collaborate with each other, regardless of each agent's underlying framework, language, or vendor. This is really important because it's paving the way for polyglot multi-agent systems.
16+
17+
=== Important Concepts
18+
19+
The A2A protocol involves a few important concepts:
20+
21+
* *User* - This is the end user who has a request that will require the help of one or more agents.
22+
* *A2A Client* - This is the client that will send requests on the user's behalf to an A2A server agent.
23+
* *A2A Server* - This is the server agent that will receive and respond to requests from an A2A client agent. An A2A server agent exposes an HTTP endpoint that implements the A2A protocol.
24+
25+
A2A client agents and A2A server agents can be implemented using different languages and frameworks. They just need to be able to speak with each other using the A2A protocol. Communication happens using JSON-RPC 2.0 over HTTP(S). A2A SDKs written for different programming languages make this interoperability possible.
26+
27+
The https://github.com/orgs/a2aproject/repositories[A2A project] aims to provide SDKs for various languages. Using the https://github.com/a2aproject/a2a-python[A2A Python SDK] and our https://github.com/a2aproject/a2a-java[A2A Java SDK], for example, it's possible for an A2A client agent written in Python to communicate with an A2A server agent written in Java and vice versa.
28+
29+
== From a Quarkus LangChain4j AI Service to an A2A Server Agent
30+
31+
Let's say we have a Quarkus LangChain4j AI service that can respond to user queries about the weather by making use of a weather MCP server:
32+
33+
[source,java]
34+
----
35+
@RegisterAiService
36+
@ApplicationScoped
37+
public interface WeatherAgent {
38+
39+
@SystemMessage("""
40+
You are a specialized weather forecast assistant. Your primary function is to
41+
utilize the provided tools to retrieve and relay weather information in response
42+
to user queries. You must rely exclusively on these tools for data and refrain
43+
from inventing information. Ensure that all responses include the detailed output
44+
from the tools used and are formatted in Markdown.
45+
"""
46+
)
47+
@McpToolBox("weather") // <-- The weather MCP server that will be used
48+
String chat(@UserMessage String question);
49+
}
50+
----
51+
52+
To turn this weather agent into an A2A server agent, there are a few simple steps we need to follow:
53+
54+
=== Add A2A Java SDK Core and Server Dependencies
55+
56+
[source,xml]
57+
----
58+
<dependency>
59+
<groupId>io.a2a.sdk</groupId>
60+
<artifactId>a2a-java-sdk-core</artifactId> <1>
61+
</dependency>
62+
<dependency>
63+
<groupId>io.a2a.sdk</groupId>
64+
<artifactId>a2a-java-sdk-server-quarkus</artifactId> <2>
65+
</dependency>
66+
----
67+
<1> `a2a-java-sdk-core` provides the core classes that make up the A2A specification, it's needed to create `AgentCards` and `AgentExecutors`. It can also be used to create A2A client agents.
68+
<2> `a2a-java-sdk-server-quarkus` provides the HTTP endpoint that implements the A2A protocol. This dependency makes use of Quarkus Reactive Routes. To make use of Jakarta REST, the `a2a-java-sdk-server-jakarta` dependency can be used instead.
69+
70+
=== Add a Class that Creates an A2A `AgentCard`
71+
72+
The `AgentCard` is a class that describes an A2A server agent's capabilities. This will be used by other agents or clients to understand what our weather agent can do. The A2A Java SDK will automatically expose this agent card at the server agent's `.well-known/agent.json` URI. For example, if our A2A server agent is running on http://localhost:10001, the agent card will be available at http://localhost:10001/.well-known/agent.json.
73+
74+
[source,java]
75+
----
76+
import io.a2a.server.PublicAgentCard;
77+
import io.a2a.spec.AgentCapabilities;
78+
import io.a2a.spec.AgentCard;
79+
import io.a2a.spec.AgentSkill;
80+
...
81+
82+
@ApplicationScoped
83+
public class WeatherAgentCardProducer {
84+
85+
@Produces
86+
@PublicAgentCard
87+
public AgentCard agentCard() {
88+
return new AgentCard.Builder()
89+
.name("Weather Agent")
90+
.description("Helps with weather")
91+
.url("http://localhost:10001") <1>
92+
.version("1.0.0")
93+
.capabilities(new AgentCapabilities.Builder() <2>
94+
.streaming(true)
95+
.pushNotifications(false)
96+
.stateTransitionHistory(false)
97+
.build())
98+
.defaultInputModes(Collections.singletonList("text"))
99+
.defaultOutputModes(Collections.singletonList("text"))
100+
.skills(Collections.singletonList(new AgentSkill.Builder()
101+
.id("weather_search")
102+
.name("Search weather")
103+
.description("Helps with weather in city, or states") <3>
104+
.tags(Collections.singletonList("weather"))
105+
.examples(List.of("weather in LA, CA")) <4>
106+
.build()))
107+
.build();
108+
}
109+
}
110+
----
111+
<1> The URL of our A2A server agent. We set `quarkus.http.port` to `10001` in our `application.properties` file so our A2A server agent will be available at http://localhost:10001.
112+
<2> Indicates the capabilities of our A2A server agent like whether it supports streaming, push notifications, and state transition history.
113+
<3> Describes what our agent can do.
114+
<4> An example query that our agent can handle.
115+
116+
=== Add a class that creates an A2A `AgentExecutor`
117+
118+
The `AgentExecutor` is a class that will be used to process requests sent to our A2A server agent. It will pass the requests received from the A2A client to our Quarkus LangChain4j AI service and is responsible for returning the responses back to the A2A client. The A2A Java SDK will call this executor when a request is sent to our A2A server agent.
119+
120+
Notice that the `AgentExecutor` interface specifies two methods, `execute` and `cancel`, that we need to implement.
121+
122+
[source,java]
123+
----
124+
import io.a2a.server.agentexecution.AgentExecutor;
125+
import io.a2a.server.agentexecution.RequestContext;
126+
import io.a2a.server.events.EventQueue;
127+
import io.a2a.server.tasks.TaskUpdater;
128+
import io.a2a.spec.JSONRPCError;
129+
import io.a2a.spec.Message;
130+
import io.a2a.spec.Part;
131+
import io.a2a.spec.Task;
132+
import io.a2a.spec.TaskNotCancelableError;
133+
import io.a2a.spec.TaskState;
134+
import io.a2a.spec.TextPart;
135+
...
136+
137+
@ApplicationScoped
138+
public class WeatherAgentExecutorProducer {
139+
140+
@Inject
141+
WeatherAgent weatherAgent; <1>
142+
143+
@Produces
144+
public AgentExecutor agentExecutor() {
145+
return new WeatherAgentExecutor(weatherAgent);
146+
}
147+
148+
private static class WeatherAgentExecutor implements AgentExecutor {
149+
150+
private final WeatherAgent weatherAgent;
151+
152+
public WeatherAgentExecutor(WeatherAgent weatherAgent) {
153+
this.weatherAgent = weatherAgent;
154+
}
155+
156+
@Override
157+
public void execute(RequestContext context, EventQueue eventQueue) throws JSONRPCError { <2>
158+
TaskUpdater updater = new TaskUpdater(context, eventQueue);
159+
160+
// mark the task as submitted and start working on it
161+
if (context.getTask() == null) {
162+
updater.submit();
163+
}
164+
updater.startWork();
165+
166+
// extract the text from the message
167+
String userMessage = extractTextFromMessage(context.getMessage());
168+
169+
// call the weather agent with the user's message
170+
String response = weatherAgent.chat(userMessage); <3>
171+
172+
// create the response part
173+
TextPart responsePart = new TextPart(response, null);
174+
List<Part<?>> parts = List.of(responsePart);
175+
176+
// add the response as an artifact and complete the task
177+
updater.addArtifact(parts, null, null, null);
178+
updater.complete();
179+
}
180+
181+
@Override
182+
public void cancel(RequestContext context, EventQueue eventQueue) throws JSONRPCError { <4>
183+
Task task = context.getTask();
184+
185+
if (task.getStatus().state() == TaskState.CANCELED) {
186+
// task already cancelled
187+
throw new TaskNotCancelableError();
188+
}
189+
190+
if (task.getStatus().state() == TaskState.COMPLETED) {
191+
// task already completed
192+
throw new TaskNotCancelableError();
193+
}
194+
195+
// cancel the task
196+
TaskUpdater updater = new TaskUpdater(context, eventQueue);
197+
updater.cancel();
198+
}
199+
200+
private String extractTextFromMessage(Message message) {
201+
StringBuilder textBuilder = new StringBuilder();
202+
if (message.getParts() != null) {
203+
for (Part part : message.getParts()) {
204+
if (part instanceof TextPart textPart) {
205+
textBuilder.append(textPart.getText());
206+
}
207+
}
208+
}
209+
return textBuilder.toString();
210+
}
211+
}
212+
}
213+
----
214+
<1> This is our Quarkus LangChain4j AI service.
215+
<2> The `execute` method will be used to process requests from an A2A client.
216+
<3> Here we are invoking our Quarkus LangChain4j AI service.
217+
<4> The `cancel` method be used to cancel an ongoing request.
218+
219+
220+
That's it, we can now start our Quarkus application as shown below and our A2A server agent will be available at http://localhost:10001. A2A client agents can now send weather-related queries to our A2A server agent and our agent will respond with the weather information.
221+
222+
[source,bash]
223+
----
224+
$ mvn quarkus:dev
225+
----
226+
227+
We've gone from a Quarkus LangChain4j AI service to an A2A server agent in just a few steps!
228+
229+
The source code for this example is available https://github.com/a2aproject/a2a-samples/tree/main/samples/multi_language/python_and_java_multiagent/weather_agent[here].
230+
231+
== Validating our A2A Server Agent Using the A2A Inspector
232+
233+
The https://github.com/a2aproject/a2a-inspector[A2A Inspector] is a web application that's very easy to run and can be used to inspect any A2A server agent.
234+
235+
We can use the A2A Inspector to validate our A2A server agent by specifying our server agent's URL in the `Connect` text box.
236+
237+
The A2A Inspector will obtain and show our server agent's agent card:
238+
239+
image::a2a-inspector-agent-card.png[scaledwidth=100%]
240+
241+
Notice that this matches the information we provided in our `WeatherAgentCardProducer` class.
242+
243+
You can also use the inspector to send requests to the A2A server agent and to view the raw HTTP requests and responses.
244+
245+
== Multi-Agent Orchestration with Python and Java Server Agents
246+
247+
Let's take a look at a more complex example that makes use of our weather A2A server agent.
248+
249+
image::multiagent-java-python.png[scaledwidth=100%]
250+
251+
This is a multi-agent example where a host agent delegates requests to two different A2A server agents, an Airbnb agent and our Weather agent, based on the user's question. Under the hood, the host agent makes use of each agent's agent card to determine the capabilities of each agent and uses an LLM to determine which agent to delegate the request to based on their capabilities.
252+
253+
The https://github.com/a2aproject/a2a-samples/tree/main/samples/multi_language/python_and_java_multiagent/airbnb_agent[Airbnb agent] is a Python agent that's implemented using LangGraph and makes use of the A2A Python SDK.
254+
255+
The https://github.com/a2aproject/a2a-samples/tree/main/samples/multi_language/python_and_java_multiagent/weather_agent[Weather agent] is our Java agent that's implemented using Quarkus LangChain4j and makes use of the A2A Java SDK.
256+
257+
Notice that the host agent uses A2A clients written in Python to communicate with the server agents. It's also possible to use an https://github.com/a2aproject/a2a-java?tab=readme-ov-file#a2a-client[A2A client] written in Java using our A2A Java SDK.
258+
259+
The complete source code for this example is available https://github.com/a2aproject/a2a-samples/tree/main/samples/multi_language/python_and_java_multiagent[here]. To experiment with this multi-agent example, try sending different types of questions to the host agent, for example:
260+
261+
* What's the weather in New York, NY?
262+
* Find me a room in LA, CA, July 7-9, 2 adults
263+
264+
Notice that the host agent will delegate the first question to the Weather agent and the second question to the Airbnb agent.
265+
266+
image::new_york_weather.png[scaledwidth=100%]
267+
268+
And the second question will be delegated to the Airbnb agent:
269+
270+
image::la_airbnb.png[scaledwidth=100%]
271+
272+
== Conclusion
273+
274+
We've seen how easy it is to get started with Quarkus and A2A using the A2A Java SDK. With just a few steps, we can turn a Quarkus LangChain4j AI service into an A2A server agent that can communicate with other A2A agents, regardless of the language or framework they are implemented in.
275+
276+
=== Further Reading
277+
278+
* https://a2aproject.github.io/A2A/specification/[A2A Specification]
279+
* https://github.com/a2aproject/a2a-java/blob/main/README.md[A2A Java SDK Documentation]
280+
281+
64.6 KB
Loading
220 KB
Loading
105 KB
Loading
70 KB
Loading

0 commit comments

Comments
 (0)