A Java library that helps run agentic applications as A2AServers following the Agent2Agent (A2A) Protocol.
You can build the A2A Java SDK using mvn
:
mvn clean install
We copy https://github.com/a2aproject/A2A/blob/main/specification/grpc/a2a.proto to the spec-grpc/
project, and adjust the java_package
option to be as follows:
option java_package = "io.a2a.grpc";
Then build the spec-grpc
module with mvn clean install -Pproto-compile
to regenerate the gRPC classes in the io.a2a.grpc
package.
You can find examples of how to use the A2A Java SDK in the a2a-samples repository.
More examples will be added soon.
The A2A Java SDK provides a Java server implementation of the Agent2Agent (A2A) Protocol. To run your agentic Java application as an A2A server, simply follow the steps below.
- Add an A2A Java SDK Server Maven dependency to your project
- Add a class that creates an A2A Agent Card
- Add a class that creates an A2A Agent Executor
Adding a dependency on an A2A Java SDK Reference Server will provide access to the core classes that make up the A2A specification and allow you to run your agentic Java application as an A2A server agent.
The A2A Java SDK provides reference A2A server implementations based on Quarkus for use with our tests and examples. However, the project is designed in such a way that it is trivial to integrate with various Java runtimes.
Server Integrations contains a list of community contributed integrations of the server with various runtimes. You might be able to use one of these for your target runtime, or you can use them as inspiration to create your own.
The A2A Java SDK Reference Server implementations support a couple transports: JSON-RPC 2.0 and gRPC.
To use the reference implementation with the JSON-RPC protocol, add the following dependency to your project:
⚠️ Theio.github.a2asdk
groupId
below is temporary and will likely change for future releases.
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-reference-jsonrpc</artifactId>
<!-- Use a released version from https://github.com/a2aproject/a2a-java/releases -->
<version>${io.a2a.sdk.version}</version>
</dependency>
To use the reference implementation with the gRPC protocol, add the following dependency to your project:
⚠️ Theio.github.a2asdk
groupId
below is temporary and will likely change for future releases.
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-reference-grpc</artifactId>
<!-- Use a released version from https://github.com/a2aproject/a2a-java/releases -->
<version>${io.a2a.sdk.version}</version>
</dependency>
Note that you can add more than one of the above dependencies to your project depending on the transports you'd like to support.
Support for the HTTP+JSON/REST transport will be coming soon.
import io.a2a.server.PublicAgentCard;
import io.a2a.spec.AgentCapabilities;
import io.a2a.spec.AgentCard;
import io.a2a.spec.AgentSkill;
...
@ApplicationScoped
public class WeatherAgentCardProducer {
@Produces
@PublicAgentCard
public AgentCard agentCard() {
return new AgentCard.Builder()
.name("Weather Agent")
.description("Helps with weather")
.url("http://localhost:10001")
.version("1.0.0")
.capabilities(new AgentCapabilities.Builder()
.streaming(true)
.pushNotifications(false)
.stateTransitionHistory(false)
.build())
.defaultInputModes(Collections.singletonList("text"))
.defaultOutputModes(Collections.singletonList("text"))
.skills(Collections.singletonList(new AgentSkill.Builder()
.id("weather_search")
.name("Search weather")
.description("Helps with weather in city, or states")
.tags(Collections.singletonList("weather"))
.examples(List.of("weather in LA, CA"))
.build()))
.protocolVersion("0.2.5")
.build();
}
}
import io.a2a.server.agentexecution.AgentExecutor;
import io.a2a.server.agentexecution.RequestContext;
import io.a2a.server.events.EventQueue;
import io.a2a.server.tasks.TaskUpdater;
import io.a2a.spec.JSONRPCError;
import io.a2a.spec.Message;
import io.a2a.spec.Part;
import io.a2a.spec.Task;
import io.a2a.spec.TaskNotCancelableError;
import io.a2a.spec.TaskState;
import io.a2a.spec.TextPart;
...
@ApplicationScoped
public class WeatherAgentExecutorProducer {
@Inject
WeatherAgent weatherAgent;
@Produces
public AgentExecutor agentExecutor() {
return new WeatherAgentExecutor(weatherAgent);
}
private static class WeatherAgentExecutor implements AgentExecutor {
private final WeatherAgent weatherAgent;
public WeatherAgentExecutor(WeatherAgent weatherAgent) {
this.weatherAgent = weatherAgent;
}
@Override
public void execute(RequestContext context, EventQueue eventQueue) throws JSONRPCError {
TaskUpdater updater = new TaskUpdater(context, eventQueue);
// mark the task as submitted and start working on it
if (context.getTask() == null) {
updater.submit();
}
updater.startWork();
// extract the text from the message
String userMessage = extractTextFromMessage(context.getMessage());
// call the weather agent with the user's message
String response = weatherAgent.chat(userMessage);
// create the response part
TextPart responsePart = new TextPart(response, null);
List<Part<?>> parts = List.of(responsePart);
// add the response as an artifact and complete the task
updater.addArtifact(parts, null, null, null);
updater.complete();
}
@Override
public void cancel(RequestContext context, EventQueue eventQueue) throws JSONRPCError {
Task task = context.getTask();
if (task.getStatus().state() == TaskState.CANCELED) {
// task already cancelled
throw new TaskNotCancelableError();
}
if (task.getStatus().state() == TaskState.COMPLETED) {
// task already completed
throw new TaskNotCancelableError();
}
// cancel the task
TaskUpdater updater = new TaskUpdater(context, eventQueue);
updater.cancel();
}
private String extractTextFromMessage(Message message) {
StringBuilder textBuilder = new StringBuilder();
if (message.getParts() != null) {
for (Part part : message.getParts()) {
if (part instanceof TextPart textPart) {
textBuilder.append(textPart.getText());
}
}
}
return textBuilder.toString();
}
}
}
The A2A Java SDK provides a Java client implementation of the Agent2Agent (A2A) Protocol, allowing communication with A2A servers. The Java client implementation currently supports two transport protocols: JSON-RPC 2.0 and gRPC.
To make use of the Java Client
:
Adding a dependency on a2a-java-sdk-client
will provide access to a ClientBuilder
that you can use to create your A2A Client
.
⚠️ Theio.github.a2asdk
groupId
below is temporary and will likely change for future releases.
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-client</artifactId>
<!-- Use a released version from https://github.com/a2aproject/a2a-java/releases -->
<version>${io.a2a.sdk.version}</version>
</dependency>
By default, the sdk-client is coming with the JSONRPC transport dependency. Despite the fact that the JSONRPC transport dependency is included by default, you still need to add the transport to the Client as described in JSON-RPC Transport section.
If you want to use another transport (such as GRPC or HTTP+JSON), you'll need to add a relevant dependency:
⚠️ Theio.github.a2asdk
groupId
below is temporary and will likely change for future releases.
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-client-transport-grpc</artifactId>
<!-- Use a released version from https://github.com/a2aproject/a2a-java/releases -->
<version>${io.a2a.sdk.version}</version>
</dependency>
Support for the HTTP+JSON/REST transport will be coming soon.
// First, get the agent card for the A2A server agent you want to connect to
AgentCard agentCard = new A2ACardResolver("http://localhost:1234").getAgentCard();
// Specify configuration for the ClientBuilder
ClientConfig clientConfig = new ClientConfig.Builder()
.setAcceptedOutputModes(List.of("text"))
.build();
// Create event consumers to handle responses that will be received from the A2A server
// (these consumers will be used for both streaming and non-streaming responses)
List<BiConsumer<ClientEvent, AgentCard>> consumers = List.of(
(event, card) -> {
if (event instanceof MessageEvent messageEvent) {
// handle the messageEvent.getMessage()
...
} else if (event instanceof TaskEvent taskEvent) {
// handle the taskEvent.getTask()
...
} else if (event instanceof TaskUpdateEvent updateEvent) {
// handle the updateEvent.getTask()
...
}
}
);
// Create a handler that will be used for any errors that occur during streaming
Consumer<Throwable> errorHandler = error -> {
// handle the error.getMessage()
...
};
// Create the client using the builder
Client client = Client
.builder(agentCard)
.clientConfig(clientConfig)
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig())
.addConsumers(consumers)
.streamingErrorHandler(errorHandler)
.build();
Different transport protocols can be configured with specific settings using specific ClientTransportConfig
implementations. The A2A Java SDK provides JSONRPCTransportConfig
for the JSON-RPC transport and GrpcTransportConfig
for the gRPC transport.
For the JSON-RPC transport, if you'd like to use the default JdkA2AHttpClient
, no additional
configuration is needed. To use a custom HTTP client implementation, simply create a JSONRPCTransportConfig
as follows:
// Create a custom HTTP client
A2AHttpClient customHttpClient = ...
// Configure the client settings
ClientConfig clientConfig = new ClientConfig.Builder()
.setAcceptedOutputModes(List.of("text"))
.build();
Client client = Client
.builder(agentCard)
.clientConfig(clientConfig)
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig(customHttpClient))
.build();
For the gRPC transport, you must configure a channel factory:
// Create a channel factory function that takes the agent URL and returns a Channel
Function<String, Channel> channelFactory = agentUrl -> {
return ManagedChannelBuilder.forTarget(agentUrl)
...
.build();
};
// Configure the client with transport-specific settings
ClientConfig clientConfig = new ClientConfig.Builder()
.setAcceptedOutputModes(List.of("text"))
.build();
Client client = Client
.builder(agentCard)
.clientConfig(clientConfig)
.withTransport(GrpcTransport.class, new GrpcTransportConfig(channelFactory))
.build();
You can specify configuration for multiple transports, the appropriate configuration will be used based on the selected transport:
// Configure both JSON-RPC and gRPC transports
Client client = Client
.builder(agentCard)
.withTransport(GrpcTransport.class, new GrpcTransportConfig(channelFactory))
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig())
.build();
// Send a text message to the A2A server agent
Message message = A2A.toUserMessage("tell me a joke");
// Send the message (uses configured consumers to handle responses)
// Streaming will automatically be used if supported by both client and server,
// otherwise the non-streaming send message method will be used automatically
client.sendMessage(message);
// You can also optionally specify a ClientCallContext with call-specific config to use
client.sendMessage(message, clientCallContext);
// Create custom consumers for this specific message
List<BiConsumer<ClientEvent, AgentCard>> customConsumers = List.of(
(event, card) -> {
// handle this specific message's responses
...
}
);
// Create custom error handler
Consumer<Throwable> customErrorHandler = error -> {
// handle the error
...
};
Message message = A2A.toUserMessage("tell me a joke");
client.sendMessage(message, customConsumers, customErrorHandler);
// Retrieve the task with id "task-1234"
Task task = client.getTask(new TaskQueryParams("task-1234"));
// You can also specify the maximum number of items of history for the task
// to include in the response and
Task task = client.getTask(new TaskQueryParams("task-1234", 10));
// You can also optionally specify a ClientCallContext with call-specific config to use
Task task = client.getTask(new TaskQueryParams("task-1234"), clientCallContext);
// Cancel the task we previously submitted with id "task-1234"
Task cancelledTask = client.cancelTask(new TaskIdParams("task-1234"));
// You can also specify additional properties using a map
Map<String, Object> metadata = Map.of("reason", "user_requested");
Task cancelledTask = client.cancelTask(new TaskIdParams("task-1234", metadata));
// You can also optionally specify a ClientCallContext with call-specific config to use
Task cancelledTask = client.cancelTask(new TaskIdParams("task-1234"), clientCallContext);
// Get task push notification configuration
TaskPushNotificationConfig config = client.getTaskPushNotificationConfiguration(
new GetTaskPushNotificationConfigParams("task-1234"));
// The push notification configuration ID can also be optionally specified
TaskPushNotificationConfig config = client.getTaskPushNotificationConfiguration(
new GetTaskPushNotificationConfigParams("task-1234", "config-4567"));
// Additional properties can be specified using a map
Map<String, Object> metadata = Map.of("source", "client");
TaskPushNotificationConfig config = client.getTaskPushNotificationConfiguration(
new GetTaskPushNotificationConfigParams("task-1234", "config-1234", metadata));
// You can also optionally specify a ClientCallContext with call-specific config to use
TaskPushNotificationConfig config = client.getTaskPushNotificationConfiguration(
new GetTaskPushNotificationConfigParams("task-1234"), clientCallContext);
// Set task push notification configuration
PushNotificationConfig pushNotificationConfig = new PushNotificationConfig.Builder()
.url("https://example.com/callback")
.authenticationInfo(new AuthenticationInfo(Collections.singletonList("jwt"), null))
.build();
TaskPushNotificationConfig taskConfig = new TaskPushNotificationConfig.Builder()
.taskId("task-1234")
.pushNotificationConfig(pushNotificationConfig)
.build();
TaskPushNotificationConfig result = client.setTaskPushNotificationConfiguration(taskConfig);
// You can also optionally specify a ClientCallContext with call-specific config to use
TaskPushNotificationConfig result = client.setTaskPushNotificationConfiguration(taskConfig, clientCallContext);
List<TaskPushNotificationConfig> configs = client.listTaskPushNotificationConfigurations(
new ListTaskPushNotificationConfigParams("task-1234"));
// Additional properties can be specified using a map
Map<String, Object> metadata = Map.of("filter", "active");
List<TaskPushNotificationConfig> configs = client.listTaskPushNotificationConfigurations(
new ListTaskPushNotificationConfigParams("task-1234", metadata));
// You can also optionally specify a ClientCallContext with call-specific config to use
List<TaskPushNotificationConfig> configs = client.listTaskPushNotificationConfigurations(
new ListTaskPushNotificationConfigParams("task-1234"), clientCallContext);
client.deleteTaskPushNotificationConfigurations(
new DeleteTaskPushNotificationConfigParams("task-1234", "config-4567"));
// Additional properties can be specified using a map
Map<String, Object> metadata = Map.of("reason", "cleanup");
client.deleteTaskPushNotificationConfigurations(
new DeleteTaskPushNotificationConfigParams("task-1234", "config-4567", metadata));
// You can also optionally specify a ClientCallContext with call-specific config to use
client.deleteTaskPushNotificationConfigurations(
new DeleteTaskPushNotificationConfigParams("task-1234", "config-4567", clientCallContext);
// Resubscribe to an ongoing task with id "task-1234" using configured consumers
TaskIdParams taskIdParams = new TaskIdParams("task-1234");
client.resubscribe(taskIdParams);
// Or resubscribe with custom consumers and error handler
List<BiConsumer<ClientEvent, AgentCard>> customConsumers = List.of(
(event, card) -> System.out.println("Resubscribe event: " + event)
);
Consumer<Throwable> customErrorHandler = error ->
System.err.println("Resubscribe error: " + error.getMessage());
client.resubscribe(taskIdParams, customConsumers, customErrorHandler);
// You can also optionally specify a ClientCallContext with call-specific config to use
client.resubscribe(taskIdParams, clientCallContext);
AgentCard serverAgentCard = client.getAgentCard();
A complete example of a Java A2A client communicating with a Python A2A server is available in the examples/helloworld/client directory. This example demonstrates:
- Setting up and using the A2A Java client
- Sending regular and streaming messages to a Python A2A server
- Receiving and processing responses from the Python A2A server
The example includes detailed instructions on how to run the Python A2A server and how to run the Java A2A client using JBang.
Check out the example's README for more information.
A complete example of a Python A2A client communicating with a Java A2A server is available in the examples/helloworld/server directory. This example demonstrates:
- A sample
AgentCard
producer - A sample
AgentExecutor
producer - A Java A2A server receiving regular and streaming messages from a Python A2A client
Check out the example's README for more information.
See COMMUNITY_ARTICLES.md for a list of community articles and videos.
This project is licensed under the terms of the Apache 2.0 License.
See CONTRIBUTING.md for contribution guidelines.
The following list contains community contributed integrations with various Java Runtimes.
To contribute an integration, please see CONTRIBUTING_INTEGRATIONS.md.
- reference/jsonrpc/README.md - JSON-RPC 2.0 Reference implementation, based on Quarkus.
- reference/grpc/README.md - gRPC Reference implementation, based on Quarkus.
- https://github.com/wildfly-extras/a2a-java-sdk-server-jakarta - This integration is based on Jakarta EE, and should work in all runtimes supporting the Jakarta EE Web Profile.