A2A Java SDK 1.2.0.Final Released
I am happy to announce the release of A2A Java SDK 1.2.0.Final . This release hardens the authorization model, adds stream lifecycle management, and fixes several protocol compliance issues.
|
NOTE
|
This release contains breaking changes . See the migration section for details. |
What's New
Authorization Hardening
The 1.1.0 release introduced the TaskAuthorizationProvider SPI for per-user task authorization. This release closes gaps in that model and makes the authorization API easier to use from non-CDI environments.
Read authorization on referenced tasks
Previously, when a request referenced existing tasks via referenceTaskIds, the SDK populated those tasks in the RequestContext without checking read authorization. An unauthorized caller could probe for task existence via sendStreamingMessage or subscribeToTask. This release enforces read authorization on all referenced task lookups ( #1005
).
The fix touches all transports (JSON-RPC, gRPC, REST) and decorators (OpenTelemetry, authorization). InMemoryTaskStore now applies fail-closed authorization -- when authorization is configured but no call context is available, access is denied -- matching the existing JpaDatabaseTaskStore behavior.
This is a breaking change -- see auth-migration below.
Referenced task population now enabled by default
Previously, when using DefaultRequestHandler via CDI, referenced task IDs in messages were not resolved from the TaskStore. This has been fixed -- referenced tasks are now populated by default and made available via RequestContext.getRelatedTasks().
If you need to restore the previous behavior, set the following property:
a2a.request-context.populate-referred-tasks=false
Fail-closed read-access helper
A new static helper TaskAuthorizationProvider.checkReadAccess() centralizes the fail-closed logic for read checks:
// Returns true (allow) when no provider is configured.
// Returns false (deny) when a provider is present but no call context is available.
// Otherwise delegates to provider.checkRead().
boolean allowed = TaskAuthorizationProvider.checkReadAccess(
provider, context, taskId, TaskOperation.GET_TASK);
This is the same logic used internally by InMemoryTaskStore and JpaDatabaseTaskStore, now available for custom TaskStore implementations.
Programmatic auth wiring
The AuthorizationRequestHandlerDecorator constructor is now public ( #966
), enabling programmatic wiring in non-CDI runtimes like Spring Framework:
RequestHandler secured = new AuthorizationRequestHandlerDecorator(
delegate, myAuthorizationProvider);
AuthenticatedUser now supports arbitrary attributes via a Map<String, Object> -- useful for carrying claims, roles, or other identity data from your authentication layer:
AuthenticatedUser user = new AuthenticatedUser("alice",
Map.of("role", "admin", "tenant", "acme"));
Object role = user.getAttribute("role"); // "admin"
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.
Task Stream Lifecycle Hook
The new TaskStreamLifecycleHook SPI ( #990
) lets you observe and control task stream lifecycle events. You can react when clients subscribe/unsubscribe to a task's event stream and when events are processed -- giving you the ability to close all active streams for a task on demand.
This is useful for patterns like closing streams after a timeout, enforcing maximum subscriber limits, or cleaning up resources when all clients disconnect.
To use it, implement the interface and register it as a CDI bean:
@ApplicationScoped
@Alternative
@Priority(1)
public class MyStreamHook implements TaskStreamLifecycleHook {
@Override
public void onSubscribe(String taskId, StreamCloseHandle handle) {
// A client subscribed -- handle.getActiveSubscriberCount() includes this subscriber
}
@Override
public void onUnsubscribe(String taskId, StreamCloseHandle handle) {
// A client disconnected
}
@Override
public void onEvent(String taskId, Event event, StreamCloseHandle handle) {
// An event was persisted and distributed -- call handle.closeStreams() to shut down all streams
}
}
The hook is wired through InMemoryQueueManager, MainEventBusProcessor, and ReplicatedQueueManager, so it works across all deployment modes. A stream-lifecycle example
with integration tests across all three transports is included.
Hardened Spec Immutability
All remaining spec records now enforce deep immutability ( #968
). Collections in spec types like SecurityRequirement, TaskArtifactUpdateEvent, TaskStatusUpdateEvent, and TextPart are defensively copied, ensuring callers cannot mutate shared state.
Multi-Version Documentation
The project website
now supports versioned documentation ( #1000
) with a version dropdown, per-version sidebar menus, and a version-scoped search filter. Each release produces a frozen documentation snapshot, while the dev.next version tracks the latest changes.
Aggregated Javadoc
A new site-javadoc Maven profile ( #988
) generates unified cross-module Javadoc where @see and @link references across modules resolve as navigable HTML links.
Bug Fixes
-
Reconcile blocking result with TaskStore -- when an
AgentExecutorreturned a result synchronously, the task state could diverge from what was persisted. The reconciliation timeout is now configurable ( #991 ) -
Avoid wrapping tasks in list responses --
ListTasksResultwas incorrectly double-wrappingTaskobjects in JSON-RPC serialization ( #998 ) -
Apply historyLength to streaming responses --
historyLengthfromMessageSendConfigurationwas silently ignored forSendStreamingMessagecalls ( #983 ) -
Correct task/contextId in emitted Messages --
AgentEmitternow populates the correcttaskIdandcontextIdon emitted messages ( #976 ) -
Deprecate isFinal overrides in AgentEmitter -- interrupted state methods now use the spec-defined
isFinalvalue instead of allowing callers to override it ( #989 ) -
Vert.x HTTP client race condition -- fixed a race in non-SSE error response handling where small error bodies could be fully received before the pipe attached ( #1005 )
Migration from 1.1.0.Final
Update your BOM version:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.a2aproject.sdk</groupId>
<artifactId>a2a-java-sdk-bom</artifactId>
<version>1.2.0.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
This release has three breaking changes:
1. Read authorization on referenced task lookup
The RequestHandler.validateRequestedTask() method has been renamed to authorizeTaskAccess() with additional parameters:
// Before
void validateRequestedTask(@Nullable String requestedTaskId) throws A2AError;
// After
void authorizeTaskAccess(@Nullable String requestedTaskId, ServerCallContext context,
TaskOperation operation) throws A2AError;
DefaultRequestHandler.create() has been replaced by a builder:
// Before
DefaultRequestHandler handler = DefaultRequestHandler.create(
agentExecutor, taskStore, queueManager, pushConfigStore,
mainEventBusProcessor, executor, eventConsumerExecutor);
// After
DefaultRequestHandler handler = DefaultRequestHandler.builder()
.agentExecutor(agentExecutor)
.taskStore(taskStore)
.queueManager(queueManager)
.pushConfigStore(pushConfigStore)
.mainEventBusProcessor(mainEventBusProcessor)
.executor(executor)
.eventConsumerExecutor(eventConsumerExecutor)
.authorizationProvider(authProvider) // optional
.populateReferredTasks(true) // optional
.build();
SimpleRequestContextBuilder now requires a third parameter:
// Before
new SimpleRequestContextBuilder(taskStore, shouldPopulateReferredTasks)
// After -- pass null if you don't use task authorization
new SimpleRequestContextBuilder(taskStore, shouldPopulateReferredTasks, authorizationProvider)
2. Split packages resolved
Several packages were renamed so that no Java package spans multiple Maven modules. This affects your imports:
| Module | Old Package | New Package |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The most common change: utility classes like CollectionCopies, ErrorDetail, PageToken, and Utils move from org.a2aproject.sdk.util to org.a2aproject.sdk.spec.util.
SPI service files were updated, so ServiceLoader-based discovery continues to work automatically.
3. TaskState.UNRECOGNIZED renamed
TaskState.UNRECOGNIZED has been renamed to TaskState.TASK_STATE_UNSPECIFIED to match the A2A specification. Its isFinal property also changed from true to false:
// Before
TaskState.UNRECOGNIZED // isFinal() == true
// After
TaskState.TASK_STATE_UNSPECIFIED // isFinal() == false
If your code relied on UNRECOGNIZED being terminal, review your logic -- event queues will no longer auto-close and clients will not stop polling when a task is in this state.
Contributors
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: