A2A Java SDK 1.3.0.Final Released
I am happy to announce the release of A2A Java SDK 1.3.0.Final . This release introduces multi-tenancy support, switches the authorization model to fail-closed by default, delivers a wave of security hardening, and improves protocol compliance across all transports.
|
NOTE
|
This release contains breaking changes . See the migration section for details. |
What's New
Multi-Tenancy Support
The headline feature of 1.3.0 is multi-tenancy ( #1084
), which lets a single A2A server provide different agent behavior per tenant. Each tenant can have its own AgentExecutor (business logic) and AgentCard (capabilities, skills, metadata). Requests without a recognized tenant automatically fall back to the default beans.
Two new SPIs in server-common define the routing contract:
-
AgentExecutorRouter-- resolves anAgentExecutorfor each request based on the tenant identifier. -
AgentCardRouter-- resolves the appropriateAgentCardfor tenant-specificgetExtendedAgentCardand public card endpoints.
A CDI-based implementation is provided in the new a2a-java-extras-multitenancy module. Use the @Tenant qualifier on CDI producer methods to wire tenant-specific beans:
@Produces
@Tenant("acme")
public AgentExecutor acmeExecutor() {
return new AcmeAgentExecutor();
}
@Produces
@Tenant("acme")
@ExtendedAgentCard
public AgentCard acmeCard() {
return AgentCard.builder().name("Acme Agent")...build();
}
The resolved tenant is available in RequestContext.getTenant() during execution. Tenant-specific public cards are served at /.well-known/{tenant}/agent-card.json. Tenant identifiers are restricted to a-zA-Z0-9_-. characters.
When a2a-java-extras-multitenancy is not on the classpath, the server behaves as a single-tenant deployment and no code changes are required.
This is a breaking change -- see multitenancy-migration below.
Fail-Closed Authorization Default
The authorization model now defaults to fail-closed ( #1095
, GHSA-qw47-mcm5-934w
). Authorization enforcement has been moved from the CDI-only AuthorizationRequestHandlerDecorator directly into DefaultRequestHandler, so it is enforced on both the CDI and builder paths. When no TaskAuthorizationProvider is configured, all task operations are now denied by default.
To restore the previous open behavior for single-user deployments or testing:
a2a.authorization.required=false
This is a breaking change -- see auth-migration below.
Security Hardening
This release includes a comprehensive set of security fixes addressing multiple vulnerability classes:
Credential leakage prevention
The SDK now validates API key header names against a safe allowlist and disables automatic HTTP redirect following in all HTTP clients (JDK, Vert.x, Android). This prevents credential leakage via header injection and cross-origin redirects ( #1097 , GHSA-9rhm-2h4x-jwmx ).
SSRF protection for push notifications
Push notification URLs are now validated against an SSRF-safe policy: only allowed schemes, with private-network blocking including IPv4-mapped IPv6. HTTP redirect following is also disabled on push notification POST requests. A new a2a.push-notifications.enabled property gates push config storage ( #1096
, GHSA-q78c-5jjq-57g8
).
Additional hardening
-
Authorization for listTasks -- the
onListTaskshandler now performs a list-scoped read check before delegation ( #1038 ) -
Error message sanitization -- internal error messages are now sanitized to prevent information disclosure ( GHSA-x32g-jvvm-4725 )
-
CR/LF injection prevention -- push notification headers are validated to reject CR/LF characters (CWE-113) ( #1053 )
Threat model
A threat model has been added to SECURITY.md ( #1090 ) to document the SDK's attack surface and security assumptions.
Security reporting
We would like to thank the community members who responsibly reported security issues. If you discover a security vulnerability, please follow the process described in our SECURITY.md -- your reports help us keep the SDK safe for everyone.
AgentExecutor as Functional Interface
AgentExecutor is now a @FunctionalInterface with a default no-op cancel method ( #1028
). Simple agents that don't need cancellation support can be implemented as lambdas:
AgentExecutor executor = (context, emitter) -> {
emitter.text("Hello!");
emitter.done();
};
Existing implementations with an explicit cancel override are unaffected.
CDI HTTP Client Provider
The new a2a-java-extras-http-client-cdi module ( #1071
) adds a CdiA2AHttpClientProvider that resolves a user-provided A2AHttpClient bean from CDI at priority 200. The A2AHttpClientFactory now sorts providers by priority at class init and iterates in order, catching exceptions per-provider for graceful fallback.
Protocol Compliance
Several protocol compliance issues have been addressed:
Bug Fixes
-
Enforce task state-machine transitions -- tasks now follow strict state-machine transition rules, preventing invalid state changes. Cancel operations are serialized to avoid races ( #1045 )
-
Per-task push notification config limit -- the
PushNotificationConfigStorenow enforces a per-task limit on push notification configurations ( #1044 ) -
Hardened event consumer and queue -- event processing is now more resilient to concurrent access patterns ( #1040 )
-
Guard limitTaskHistory against negative values --
limitTaskHistoryno longer accepts negativehistoryLengthvalues ( #1039 ) -
Thread-safe InMemoryPushNotificationConfigStore -- replaced
synchronizedMapwithConcurrentHashMapusing atomic per-key operations and immutable snapshot storage ( #1057 ) -
Deferred AgentCard resolution -- all transport handlers now resolve
AgentCardlazily to prevent startup failures when the card producer depends on the HTTP server's bound address ( #1058 ) -
gRPC transport validation -- improved validation and error messages in the gRPC transport layer ( #1026 )
Migration from 1.2.0.Final
Update your BOM version:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.a2aproject.sdk</groupId>
<artifactId>a2a-java-sdk-bom</artifactId>
<version>1.3.0.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
This release has two breaking changes:
1. Multi-tenancy routing changes DefaultRequestHandler
The introduction of AgentExecutorRouter and AgentCardRouter changes the DefaultRequestHandler constructor and builder. If you wire DefaultRequestHandler programmatically, update to the new API:
// Single-tenant: unchanged usage
DefaultRequestHandler handler = DefaultRequestHandler.builder()
.agentExecutor(agentExecutor)
.taskStore(taskStore)
.queueManager(queueManager)
// ...
.build();
For multi-tenant deployments without CDI, supply an AgentExecutorRouter directly on the builder:
Map<String, AgentExecutor> executors = Map.of(
"acme", new AcmeAgentExecutor(),
"beta", new BetaAgentExecutor()
);
AgentExecutor defaultExecutor = new DefaultAgentExecutor();
AgentExecutorRouter router = tenant ->
executors.getOrDefault(tenant, defaultExecutor);
DefaultRequestHandler handler = DefaultRequestHandler.builder()
.agentExecutorRouter(router) // replaces .agentExecutor() for multi-tenant
.taskStore(taskStore)
.queueManager(queueManager)
// ...
.build();
CDI-based deployments pick up the routers automatically when a2a-java-extras-multitenancy is on the classpath.
GetExtendedAgentCardRequest now carries GetExtendedAgentCardParams to support tenant-specific card resolution.
2. Fail-closed authorization default
Applications without a TaskAuthorizationProvider will now reject all task operations by default. Either:
-
Provide a
TaskAuthorizationProviderimplementation, or -
Set
a2a.authorization.required=falseto restore the previous open behavior
# For development/testing or single-user deployments
a2a.authorization.required=false
Contributors
Thank you to the contributors of this release!
@ehsavoie , @kabir , @ez-lbz , @JakubWorek , @malladinagarjuna2 , @omatheusmesmo , @ruilopes , @KXH
Come Join Us
We value your feedback a lot so please report bugs, ask for improvements etc. Let's build something great together!
If you are an A2A Java SDK user or just curious, don't be shy and join our welcoming community: