A2A Server

The A2A Java SDK provides a Java server implementation of the Agent2Agent (A2A) Protocol. To run your agentic Java application as an A2A server, follow the steps below.

Supported Transports

  • JSON-RPC 2.0
  • gRPC
  • HTTP+JSON/REST

1. Add a Server Dependency

JSON-RPC

<dependency>
    <groupId>org.a2aproject.sdk</groupId>
    <artifactId>a2a-java-sdk-reference-jsonrpc</artifactId>
    <!-- Use a released version from https://github.com/a2aproject/a2a-java/releases -->
    <version>${org.a2aproject.sdk.version}</version>
</dependency>

gRPC

<dependency>
    <groupId>org.a2aproject.sdk</groupId>
    <artifactId>a2a-java-sdk-reference-grpc</artifactId>
    <version>${org.a2aproject.sdk.version}</version>
</dependency>

HTTP+JSON/REST

<dependency>
    <groupId>org.a2aproject.sdk</groupId>
    <artifactId>a2a-java-sdk-reference-rest</artifactId>
    <version>${org.a2aproject.sdk.version}</version>
</dependency>

You can add more than one transport dependency to support multiple protocols simultaneously.

2. Define an Agent Card

@ApplicationScoped
public class WeatherAgentCardProducer {

    private static final String AGENT_URL = "http://localhost:10001";

    @Produces
    @PublicAgentCard
    public AgentCard agentCard() {
        return AgentCard.builder()
                .name("Weather Agent")
                .description("Helps with weather")
                .supportedInterfaces(List.of(
                        new AgentInterface(TransportProtocol.JSONRPC.asString(), AGENT_URL)))
                .version("1.0.0")
                .capabilities(AgentCapabilities.builder()
                        .streaming(true)
                        .pushNotifications(false)
                        .build())
                .defaultInputModes(Collections.singletonList("text"))
                .defaultOutputModes(Collections.singletonList("text"))
                .skills(Collections.singletonList(AgentSkill.builder()
                        .id("weather_search")
                        .name("Search weather")
                        .description("Helps with weather in cities or states")
                        .tags(Collections.singletonList("weather"))
                        .examples(List.of("weather in LA, CA"))
                        .build()))
                .build();
    }
}

3. Implement an Agent Executor

@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, AgentEmitter agentEmitter) throws JSONRPCError {
            if (context.getTask() == null) {
                agentEmitter.submit();
            }
            agentEmitter.startWork();

            String userMessage = extractTextFromMessage(context.getMessage());
            String response = weatherAgent.chat(userMessage);

            agentEmitter.addArtifact(List.of(new TextPart(response)));
            agentEmitter.complete();
        }

        @Override
        public void cancel(RequestContext context, AgentEmitter agentEmitter) throws JSONRPCError {
            Task task = context.getTask();
            if (task == null) {
                agentEmitter.cancel();
                return;
            }
            if (task.getStatus().state() == TaskState.CANCELED ||
                task.getStatus().state() == TaskState.COMPLETED) {
                throw new TaskNotCancelableError();
            }
            agentEmitter.cancel();
        }

        private String extractTextFromMessage(Message message) {
            if (message == null) {
                return "";
            }
            StringBuilder textBuilder = new StringBuilder();
            for (Part<?> part : message.parts()) {
                if (part instanceof TextPart textPart) {
                    textBuilder.append(textPart.text());
                }
            }
            return textBuilder.toString();
        }
    }
}

4. Configuration

The SDK uses META-INF/a2a-defaults.properties for defaults. Override via application.properties when using Quarkus/MicroProfile Config:

# Thread pool for async/streaming operations
a2a.executor.core-pool-size=5
a2a.executor.max-pool-size=50
a2a.executor.keep-alive-seconds=60

# Timeouts for blocking calls
a2a.blocking.agent.timeout.seconds=30
a2a.blocking.consumption.timeout.seconds=5

For LLM-based agents, increase a2a.blocking.agent.timeout.seconds to 60–120 seconds.

5. Task Authorization (Optional)

Implement TaskAuthorizationProvider to control per-user access:

@ApplicationScoped
public class MyTaskAuthorizationProvider implements TaskAuthorizationProvider {

    @Override
    public boolean checkRead(ServerCallContext context, String taskId, TaskOperation op) {
        return isOwner(context.getUser(), taskId);
    }

    @Override
    public boolean checkWrite(ServerCallContext context, String taskId, TaskOperation op) {
        return isOwner(context.getUser(), taskId);
    }

    @Override
    public boolean checkCreate(ServerCallContext context, TaskOperation op) {
        return context.getUser().isAuthenticated();
    }

    @Override
    public boolean isTaskRecorded(String taskId) {
        return ownershipStore.contains(taskId);
    }

    @Override
    public void recordOwnership(ServerCallContext context, String taskId, TaskOperation op) {
        ownershipStore.put(taskId, context.getUser().getUsername());
    }
}

The SDK discovers the bean via CDI automatically — no additional wiring needed.

Operation Authorization check
getTask, subscribeToTask, getTaskPushNotificationConfig, listTaskPushNotificationConfigs checkRead
cancelTask, createTaskPushNotificationConfig, deleteTaskPushNotificationConfig checkWrite
messageSend / messageSendStream (existing task) checkWrite
messageSend / messageSendStream (new task) checkCreate, then recordOwnership
listTasks checkRead per task

Backward Compatibility with v0.3

Add compat modules alongside v1.0 modules to serve both protocol versions simultaneously. No changes to your AgentExecutor are needed.

<!-- JSON-RPC with automatic v1.0 + v0.3 routing -->
<dependency>
    <groupId>org.a2aproject.sdk</groupId>
    <artifactId>a2a-java-sdk-reference-multiversion-jsonrpc</artifactId>
    <version>${org.a2aproject.sdk.version}</version>
</dependency>

<!-- REST with automatic v1.0 + v0.3 routing -->
<dependency>
    <groupId>org.a2aproject.sdk</groupId>
    <artifactId>a2a-java-sdk-reference-multiversion-rest</artifactId>
    <version>${org.a2aproject.sdk.version}</version>
</dependency>

Individual Compat Modules

<dependency>
    <groupId>org.a2aproject.sdk</groupId>
    <artifactId>a2a-java-sdk-compat-0.3-reference-jsonrpc</artifactId>
    <version>${org.a2aproject.sdk.version}</version>
</dependency>

Version routing uses the A2A-Version HTTP header for JSON-RPC and REST; for gRPC it is implicit via protobuf package name.

Server Integrations

  • Quarkus — Reference implementations are Quarkus-based (JSON-RPC, gRPC, REST)
  • Jakarta EEa2a-jakarta works with any Jakarta EE Web Profile runtime

See CONTRIBUTING_INTEGRATIONS.md to submit your own integration.