diff --git a/a2a/pom.xml b/a2a/pom.xml
index d98064ec8..d7034f36c 100644
--- a/a2a/pom.xml
+++ b/a2a/pom.xml
@@ -104,6 +104,11 @@
${truth.version}
test
+
+ org.mockito
+ mockito-core
+ test
+
diff --git a/contrib/samples/a2a_server/README.md b/contrib/samples/a2a_server/README.md
new file mode 100644
index 000000000..5351e32c0
--- /dev/null
+++ b/contrib/samples/a2a_server/README.md
@@ -0,0 +1,70 @@
+# Google ADK A2A Agent Server Sample
+
+This sample demonstrates how to expose a Google ADK (Agent Development Kit)
+agent via the A2A (Agent-to-Agent) protocol using A2A SDK and Quarkus service.
+
+## Overview
+
+The application implements a simple conversational agent that checks whether
+given numbers are prime numbers. It uses the `LlmAgent` from the Google ADK and
+exposes it via an A2A server.
+
+### Key Components
+
+* **`Agent.java`**: Defines the `LlmAgent` instance (`check_prime_agent`) and
+ the `checkPrime` tool function it uses to verify numbers.
+* **`AgentCardProducer.java`**: Loads and provides the `AgentCard` metadata
+ (from `agent.json`) which defines the agent's identity and capabilities in
+ the A2A network.
+* **`AgentExecutorProducer.java`**: Configures and provides the A2A
+ `AgentExecutor`, implemented by the ADK library to wire ADK-owned agents
+ automatically.
+* **`StartupConfig.java`**: Contains initialization logic, such as registering
+ JSON modules for the Vert.x/Quarkus runtime.
+* **`application.properties`**: Contains a configuration for the Quarkus
+ service and A2A, such as port where application will be exposed, application
+ name and event processing timeouts.
+
+## Building the Project
+
+You can build the project using Maven:
+
+```shell
+mvn clean install
+```
+
+The Java server can be started using `mvn` as follow (don't forget to set your
+GOOGLE_API_KEY before running the service):
+
+```bash
+export GOOGLE_API_KEY=
+
+cd contrib/samples/a2a_server
+mvn quarkus:dev
+```
+
+## Sample request
+
+```bash
+curl -X POST http://localhost:9090 \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "jsonrpc": "2.0",
+ "id": "cli-check-2",
+ "method": "message/stream",
+ "params": {
+ "message": {
+ "kind": "message",
+ "contextId": "cli-demo-context",
+ "messageId": "cli-check-2",
+ "role": "user",
+ "parts": [
+ {
+ "kind": "text",
+ "text": "Is 2 prime?"
+ }
+ ]
+ }
+ }
+ }'
+```
diff --git a/contrib/samples/a2a_server/pom.xml b/contrib/samples/a2a_server/pom.xml
new file mode 100644
index 000000000..d47a0c984
--- /dev/null
+++ b/contrib/samples/a2a_server/pom.xml
@@ -0,0 +1,142 @@
+
+
+ 4.0.0
+
+
+ com.google.adk
+ google-adk-samples
+ 0.7.1-SNAPSHOT
+ ..
+
+
+ google-adk-sample-a2a-agent
+ jar
+
+ Google ADK - Sample - A2A Agent Server
+ Demonstrates exposing ADK agent via A2A.
+
+
+ UTF-8
+ 17
+ ${project.version}
+ ${project.version}
+ 0.3.0.Beta1
+ 3.30.6
+ 0.8
+
+
+
+
+
+ io.quarkus
+ quarkus-bom
+ ${quarkus.platform.version}
+ pom
+ import
+
+
+
+
+
+
+ io.github.a2asdk
+ a2a-java-sdk-reference-jsonrpc
+ ${a2a.sdk.version}
+
+
+ io.quarkus
+ quarkus-resteasy-jackson
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+ com.google.adk
+ google-adk
+ ${google-adk.version}
+
+
+ com.google.adk
+ google-adk-a2a
+ ${google-adk-a2a.version}
+
+
+
+ io.github.a2asdk
+ a2a-java-sdk-spec
+ ${a2a.sdk.version}
+
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-reactive-routes
+
+
+ io.quarkus
+ quarkus-jackson
+
+
+
+ io.github.a2asdk
+ a2a-java-sdk-client
+ ${a2a.sdk.version}
+
+
+
+ com.google.flogger
+ flogger
+ ${flogger.version}
+
+
+
+ com.google.flogger
+ google-extensions
+ ${flogger.version}
+
+
+
+ com.google.flogger
+ flogger-system-backend
+ ${flogger.version}
+
+
+
+
+
+
+
+ src/main/java
+
+ **/*.json
+
+
+
+ src/main/resources
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+ ${quarkus.platform.version}
+ true
+
+
+
+ build
+ generate-code
+ generate-code-tests
+
+
+
+
+
+
+
diff --git a/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/AgentCardProducer.java b/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/AgentCardProducer.java
new file mode 100644
index 000000000..0937e2512
--- /dev/null
+++ b/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/AgentCardProducer.java
@@ -0,0 +1,33 @@
+package com.google.adk.samples.a2aagent;
+
+import io.a2a.server.PublicAgentCard;
+import io.a2a.spec.AgentCard;
+import io.a2a.util.Utils;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Produces;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+/** Produces the {@link AgentCard} from the bundled JSON resources. */
+@ApplicationScoped
+public class AgentCardProducer {
+
+ @Produces
+ @PublicAgentCard
+ public AgentCard agentCard() {
+ try (InputStream is = getClass().getResourceAsStream("/agent/agent.json")) {
+ if (is == null) {
+ throw new RuntimeException("agent.json not found in resources");
+ }
+
+ // Read the JSON file content
+ String json = new String(is.readAllBytes(), StandardCharsets.UTF_8);
+
+ // Use the SDK's built-in mapper to convert JSON string to AgentCard record
+ return Utils.OBJECT_MAPPER.readValue(json, AgentCard.class);
+
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load AgentCard from JSON", e);
+ }
+ }
+}
diff --git a/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/AgentExecutorProducer.java b/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/AgentExecutorProducer.java
new file mode 100644
index 000000000..4ecd2517d
--- /dev/null
+++ b/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/AgentExecutorProducer.java
@@ -0,0 +1,28 @@
+package com.google.adk.samples.a2aagent;
+
+import com.google.adk.a2a.executor.AgentExecutorConfig;
+import com.google.adk.samples.a2aagent.agent.Agent;
+import com.google.adk.sessions.InMemorySessionService;
+import io.a2a.server.agentexecution.AgentExecutor;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Produces;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+/** Produces the {@link AgentExecutor} instance that handles agent interactions. */
+@ApplicationScoped
+public class AgentExecutorProducer {
+
+ @ConfigProperty(name = "my.adk.app.name", defaultValue = "default-app")
+ String appName;
+
+ @Produces
+ public AgentExecutor agentExecutor() {
+ InMemorySessionService sessionService = new InMemorySessionService();
+ return new com.google.adk.a2a.executor.AgentExecutor.Builder()
+ .agent(Agent.ROOT_AGENT)
+ .appName(appName)
+ .sessionService(sessionService)
+ .agentExecutorConfig(AgentExecutorConfig.builder().build())
+ .build();
+ }
+}
diff --git a/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/StartupConfig.java b/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/StartupConfig.java
new file mode 100644
index 000000000..0da70b086
--- /dev/null
+++ b/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/StartupConfig.java
@@ -0,0 +1,17 @@
+package com.google.adk.samples.a2aagent;
+
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import io.quarkus.runtime.StartupEvent;
+import io.vertx.core.json.jackson.DatabindCodec;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.event.Observes;
+
+/** Configuration applied on startup, such as Jackson module registrations. */
+@ApplicationScoped
+public class StartupConfig {
+
+ void onStart(@Observes StartupEvent ev) {
+ // Register globally for Vert.x's internal JSON handling
+ DatabindCodec.mapper().registerModule(new JavaTimeModule());
+ }
+}
diff --git a/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/agent/Agent.java b/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/agent/Agent.java
new file mode 100644
index 000000000..a415f618f
--- /dev/null
+++ b/contrib/samples/a2a_server/src/main/java/com/google/adk/samples/a2aagent/agent/Agent.java
@@ -0,0 +1,107 @@
+package com.google.adk.samples.a2aagent.agent;
+
+import static java.util.stream.Collectors.joining;
+
+import com.google.adk.agents.LlmAgent;
+import com.google.adk.tools.FunctionTool;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.flogger.GoogleLogger;
+import io.reactivex.rxjava3.core.Maybe;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Agent that can check whether numbers are prime. */
+public final class Agent {
+
+ private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
+
+ /**
+ * Checks if a list of numbers are prime.
+ *
+ * @param nums The list of numbers to check
+ * @return A map containing the result message
+ */
+ public static ImmutableMap checkPrime(List nums) {
+ logger.atInfo().log("checkPrime called with nums=%s", nums);
+ Set primes = new HashSet<>();
+ for (int num : nums) {
+ if (num <= 1) {
+ continue;
+ }
+ boolean isPrime = true;
+ for (int i = 2; i <= Math.sqrt(num); i++) {
+ if (num % i == 0) {
+ isPrime = false;
+ break;
+ }
+ }
+ if (isPrime) {
+ primes.add(num);
+ }
+ }
+ String result;
+ if (primes.isEmpty()) {
+ result = "No prime numbers found.";
+ } else if (primes.size() == 1) {
+ int only = primes.iterator().next();
+ // Per request: singular phrasing without article
+ result = only + " is prime number.";
+ } else {
+ result = primes.stream().map(String::valueOf).collect(joining(", ")) + " are prime numbers.";
+ }
+ logger.atInfo().log("checkPrime result=%s", result);
+ return ImmutableMap.of("result", result);
+ }
+
+ public static final LlmAgent ROOT_AGENT =
+ LlmAgent.builder()
+ .model("gemini-2.5-pro")
+ .name("check_prime_agent")
+ .description("check prime agent that can check whether numbers are prime.")
+ .instruction(
+ """
+ You check whether numbers are prime.
+
+ If the last user message contains numbers, call checkPrime exactly once with exactly
+ those integers as a list (e.g., [2]). Never add other numbers. Do not ask for
+ clarification. Return only the tool's result.
+
+ Always pass a list of integers to the tool (use a single-element list for one
+ number). Never pass strings.
+ """)
+ // Log the exact contents passed to the LLM request for verification
+ .beforeModelCallback(
+ (callbackContext, llmRequest) -> {
+ try {
+ logger.atInfo().log(
+ "Invocation events (count=%d): %s",
+ callbackContext.events().size(), callbackContext.events());
+ } catch (Throwable t) {
+ logger.atWarning().withCause(t).log("BeforeModel logging error");
+ }
+ return Maybe.empty();
+ })
+ .afterModelCallback(
+ (callbackContext, llmResponse) -> {
+ try {
+ String content =
+ llmResponse.content().map(Object::toString).orElse("");
+ logger.atInfo().log("AfterModel content=%s", content);
+ llmResponse
+ .errorMessage()
+ .ifPresent(
+ error ->
+ logger.atInfo().log(
+ "AfterModel errorMessage=%s", error.replace("\n", "\\n")));
+ } catch (Throwable t) {
+ logger.atWarning().withCause(t).log("AfterModel logging error");
+ }
+ return Maybe.empty();
+ })
+ .tools(ImmutableList.of(FunctionTool.create(Agent.class, "checkPrime")))
+ .build();
+
+ private Agent() {}
+}
diff --git a/contrib/samples/a2a_server/src/main/resources/agent/agent.json b/contrib/samples/a2a_server/src/main/resources/agent/agent.json
new file mode 100644
index 000000000..4a0848282
--- /dev/null
+++ b/contrib/samples/a2a_server/src/main/resources/agent/agent.json
@@ -0,0 +1,18 @@
+{
+ "capabilities": {"streaming": true},
+ "defaultInputModes": ["text/plain"],
+ "defaultOutputModes": ["application/json"],
+ "description": "An agent specialized in checking whether numbers are prime. It can efficiently determine the primality of individual numbers or lists of numbers.",
+ "name": "check_prime_agent",
+ "skills": [
+ {
+ "id": "prime_checking",
+ "name": "Prime Number Checking",
+ "description": "Check if numbers in a list are prime using efficient mathematical algorithms",
+ "tags": ["mathematical", "computation", "prime", "numbers"]
+ }
+ ],
+ "preferredTransport": "JSONRPC",
+ "url": "http://localhost:9090",
+ "version": "1.0.0"
+}
diff --git a/contrib/samples/a2a_server/src/main/resources/application.properties b/contrib/samples/a2a_server/src/main/resources/application.properties
new file mode 100644
index 000000000..ba7a5f2b0
--- /dev/null
+++ b/contrib/samples/a2a_server/src/main/resources/application.properties
@@ -0,0 +1,10 @@
+# Timeout for the agent to complete execution (default 30s)
+a2a.blocking.agent.timeout.seconds=30
+
+# Timeout for final event processing (default 5s)
+a2a.blocking.consumption.timeout.seconds=5
+
+# Custom application name for the ADK Runner
+my.adk.app.name=My-JSONRPC-Agent
+
+quarkus.http.port=9090
\ No newline at end of file
diff --git a/contrib/samples/pom.xml b/contrib/samples/pom.xml
index 84a4898d6..f3c8359b8 100644
--- a/contrib/samples/pom.xml
+++ b/contrib/samples/pom.xml
@@ -17,6 +17,7 @@
a2a_basic
+ a2a_server
configagent
helloworld
mcpfilesystem