Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
---
layout: post
title: 'A2A Java SDK 1.2.0.Final Released'
date: 2026-08-06
tags: ai a2a
synopsis: 'A2A Java SDK 1.2.0.Final is now available -- with authorization hardening, stream lifecycle hooks, and improved spec compliance.'
author: kkhan
---
image::announce.png[A2A Java SDK 1.2.0.Final announcement]

I am happy to announce the release of link:https://github.com/a2aproject/a2a-java/releases/tag/v1.2.0.Final[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 (link:https://github.com/a2aproject/a2a-java/pull/1005[\#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:

[source,properties]
----
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:

[source,java]
----
// 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` (link:https://github.com/a2aproject/a2a-java/pull/966[\#966]), enabling programmatic wiring in non-CDI runtimes like Spring Framework:

[source,java]
----
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:

[source,java]
----
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 link:https://github.com/a2aproject/a2a-java/blob/main/SECURITY.md[SECURITY.md] -- your reports help us keep the SDK safe for everyone.

=== Task Stream Lifecycle Hook

The new `TaskStreamLifecycleHook` SPI (link:https://github.com/a2aproject/a2a-java/issues/990[\#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:

[source,java]
----
@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 link:https://github.com/a2aproject/a2a-java/tree/main/examples/stream-lifecycle[stream-lifecycle example] with integration tests across all three transports is included.

=== Hardened Spec Immutability

All remaining spec records now enforce deep immutability (link:https://github.com/a2aproject/a2a-java/pull/968[\#968]). Collections in spec types like `SecurityRequirement`, `TaskArtifactUpdateEvent`, `TaskStatusUpdateEvent`, and `TextPart` are defensively copied, ensuring callers cannot mutate shared state.

=== Multi-Version Documentation

The link:https://a2aproject.github.io/a2a-java/[project website] now supports versioned documentation (link:https://github.com/a2aproject/a2a-java/pull/1000[\#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 (link:https://github.com/a2aproject/a2a-java/pull/988[\#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 `AgentExecutor` returned a result synchronously, the task state could diverge from what was persisted. The reconciliation timeout is now configurable (link:https://github.com/a2aproject/a2a-java/pull/991[\#991])
* **Avoid wrapping tasks in list responses** -- `ListTasksResult` was incorrectly double-wrapping `Task` objects in JSON-RPC serialization (link:https://github.com/a2aproject/a2a-java/pull/998[\#998])
* **Apply historyLength to streaming responses** -- `historyLength` from `MessageSendConfiguration` was silently ignored for `SendStreamingMessage` calls (link:https://github.com/a2aproject/a2a-java/pull/983[\#983])
* **Correct task/contextId in emitted Messages** -- `AgentEmitter` now populates the correct `taskId` and `contextId` on emitted messages (link:https://github.com/a2aproject/a2a-java/pull/976[\#976])
* **Deprecate isFinal overrides in AgentEmitter** -- interrupted state methods now use the spec-defined `isFinal` value instead of allowing callers to override it (link:https://github.com/a2aproject/a2a-java/pull/989[\#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 (link:https://github.com/a2aproject/a2a-java/pull/1005[\#1005])

[[migration]]
== Migration from 1.1.0.Final

Update your BOM version:

[source,xml]
----
<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:

[[auth-migration]]
=== 1. Read authorization on referenced task lookup

The `RequestHandler.validateRequestedTask()` method has been renamed to `authorizeTaskAccess()` with additional parameters:

[source,java]
----
// 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:

[source,java]
----
// 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:

[source,java]
----
// 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:

[cols="1,1,1"]
|===
| Module | Old Package | New Package

| `spec`
| `org.a2aproject.sdk.util`
| `org.a2aproject.sdk.spec.util`

| `extras/opentelemetry`
| `org.a2aproject.sdk.extras.opentelemetry`
| `org.a2aproject.sdk.extras.opentelemetry.server`

| `extras/http-client-android`
| `org.a2aproject.sdk.client.http`
| `org.a2aproject.sdk.client.http.android`

| `extras/http-client-vertx`
| `org.a2aproject.sdk.client.http`
| `org.a2aproject.sdk.client.http.vertx`
|===

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`:

[source,java]
----
// 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

Thank you to the contributors of this release!

link:https://github.com/ehsavoie[@ehsavoie], link:https://github.com/kabir[@kabir], link:https://github.com/Sh1bari[@Sh1bari], link:https://github.com/JakubWorek[@JakubWorek], link:https://github.com/014-code[@014-code], link:https://github.com/jstar0[@jstar0]

== Resources

* link:https://github.com/a2aproject/a2a-java/releases/tag/v1.2.0.Final[Release Notes on GitHub]
* link:https://central.sonatype.com/artifact/org.a2aproject.sdk/a2a-java-sdk-parent/1.2.0.Final[Maven Central]
* link:https://javadoc.io/doc/org.a2aproject.sdk/[JavaDoc]
* link:https://a2a-protocol.org/v1.0.0/specification/[A2A Specification]
* link:https://a2aproject.github.io/a2a-java/[Project Website]
* link:https://github.com/a2aproject/a2a-java/tree/main/examples[Examples]

== 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:

* provide feedback on link:https://github.com/a2aproject/a2a-java/issues[GitHub];
* craft some code and link:https://github.com/a2aproject/a2a-java/pulls[push a PR];
* discuss with us in the `#a2a-java` channel on link:https://discord.gg/jTtSkJB74Q[Discord];
27 changes: 27 additions & 0 deletions docs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<skipITs>true</skipITs>
<roq.version>2.1.6</roq.version>
<roq.version>2.1.4</roq.version>
<maven.compiler.release>21</maven.compiler.release>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -56,6 +58,11 @@
<artifactId>quarkus-roq-plugin-markdown</artifactId>
<version>${roq.version}</version>
</dependency>
<dependency>
<groupId>io.quarkiverse.roq</groupId>
<artifactId>quarkus-roq-plugin-asciidoc</artifactId>
<version>${roq.version}</version>
</dependency>
<dependency>
<groupId>io.quarkiverse.roq</groupId>
<artifactId>quarkus-roq-plugin-sitemap</artifactId>
Expand Down Expand Up @@ -96,6 +103,26 @@

<build>
<plugins>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>${maven-enforcer-plugin.version}</version>
<executions>
<execution>
<id>require-java-21</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[21,)</version>
<message>The docs module requires Java 21+ (Roq ${roq.version} is compiled for Java 21)</message>
</requireJavaVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.quarkus.platform</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
Expand Down
Loading