From 4ca08a8321ded70440dfd0d8361489995ffdae5e Mon Sep 17 00:00:00 2001 From: Kabir Khan Date: Thu, 8 Jan 2026 14:47:47 +0000 Subject: [PATCH 1/3] fix: Add nooargs constructors so we work in Jakarta --- .../replicated/core/ReplicatedQueueManager.java | 11 +++++++++++ .../a2a/server/events/InMemoryQueueManager.java | 10 ++++++++++ .../requesthandlers/DefaultRequestHandler.java | 17 +++++++++++++++++ .../tasks/BasePushNotificationSender.java | 10 ++++++++++ .../jsonrpc/handler/JSONRPCHandler.java | 9 +++++++++ 5 files changed, 57 insertions(+) diff --git a/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java b/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java index 88c0c75ab..1418839d2 100644 --- a/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java +++ b/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java @@ -30,6 +30,17 @@ public class ReplicatedQueueManager implements QueueManager { private TaskStateProvider taskStateProvider; + /** + * No-args constructor for CDI proxy creation. + */ + @SuppressWarnings("NullAway") + protected ReplicatedQueueManager() { + // For CDI proxy creation + this.delegate = null; + this.replicationStrategy = null; + this.taskStateProvider = null; + } + @Inject public ReplicatedQueueManager(ReplicationStrategy replicationStrategy, TaskStateProvider taskStateProvider) { this.replicationStrategy = replicationStrategy; diff --git a/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java b/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java index 19179f50d..e27c888f5 100644 --- a/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java +++ b/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java @@ -19,6 +19,16 @@ public class InMemoryQueueManager implements QueueManager { private final EventQueueFactory factory; private final TaskStateProvider taskStateProvider; + /** + * No-args constructor for CDI proxy creation. + */ + @SuppressWarnings("NullAway") + protected InMemoryQueueManager() { + // For CDI proxy creation + this.factory = null; + this.taskStateProvider = null; + } + @Inject public InMemoryQueueManager(TaskStateProvider taskStateProvider) { this.factory = new DefaultEventQueueFactory(); diff --git a/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java b/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java index 61af1d43e..6f7ffcbe2 100644 --- a/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java +++ b/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java @@ -220,6 +220,23 @@ public class DefaultRequestHandler implements RequestHandler { private final Executor executor; + /** + * No-args constructor for CDI proxy creation. + * CDI requires a non-private constructor to create proxies for @ApplicationScoped beans. + * All fields are initialized by the @Inject constructor during actual bean creation. + */ + @SuppressWarnings("NullAway") + protected DefaultRequestHandler() { + // For CDI proxy creation + this.agentExecutor = null; + this.taskStore = null; + this.queueManager = null; + this.pushConfigStore = null; + this.pushSender = null; + this.requestContextBuilder = null; + this.executor = null; + } + @Inject public DefaultRequestHandler(AgentExecutor agentExecutor, TaskStore taskStore, QueueManager queueManager, PushNotificationConfigStore pushConfigStore, diff --git a/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java b/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java index 35a248373..388284089 100644 --- a/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java +++ b/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java @@ -30,6 +30,16 @@ public class BasePushNotificationSender implements PushNotificationSender { private final A2AHttpClient httpClient; private final PushNotificationConfigStore configStore; + /** + * No-args constructor for CDI proxy creation. + */ + @SuppressWarnings("NullAway") + protected BasePushNotificationSender() { + // For CDI proxy creation + this.httpClient = null; + this.configStore = null; + } + @Inject public BasePushNotificationSender(PushNotificationConfigStore configStore) { this.httpClient = new JdkA2AHttpClient(); diff --git a/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java b/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java index 06de11dcd..1410aa6c8 100644 --- a/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java +++ b/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java @@ -65,6 +65,15 @@ public class JSONRPCHandler { private RequestHandler requestHandler; private final Executor executor; + @SuppressWarnings("NullAway") + protected JSONRPCHandler() { + // For CDI proxy creation + this.agentCard = null; + this.extendedAgentCard = null; + this.requestHandler = null; + this.executor = null; + } + @Inject public JSONRPCHandler(@PublicAgentCard AgentCard agentCard, @Nullable @ExtendedAgentCard Instance extendedAgentCard, RequestHandler requestHandler, @Internal Executor executor) { From 2eb6621eda49bef5583c482f17ffea88ea32892f Mon Sep 17 00:00:00 2001 From: Kabir Khan Date: Fri, 9 Jan 2026 16:46:39 +0000 Subject: [PATCH 2/3] Make injected fields non-final --- .../core/ReplicatedQueueManager.java | 7 ++++--- .../server/events/InMemoryQueueManager.java | 7 +++++-- .../requesthandlers/DefaultRequestHandler.java | 18 ++++++++++-------- .../tasks/BasePushNotificationSender.java | 7 +++++-- .../transport/grpc/handler/GrpcHandler.java | 2 +- .../jsonrpc/handler/JSONRPCHandler.java | 5 ++++- .../transport/rest/handler/RestHandler.java | 6 +++++- 7 files changed, 34 insertions(+), 18 deletions(-) diff --git a/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java b/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java index 1418839d2..ffc06783c 100644 --- a/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java +++ b/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java @@ -24,10 +24,11 @@ public class ReplicatedQueueManager implements QueueManager { private static final Logger LOGGER = LoggerFactory.getLogger(ReplicatedQueueManager.class); - private final InMemoryQueueManager delegate; - + // Fields set by constructor injection cannot be final. We need a noargs constructor for + // Jakarta compatibility, and it seems that making fields set by constructor injection + // final, is not proxyable in all runtimes + private InMemoryQueueManager delegate; private ReplicationStrategy replicationStrategy; - private TaskStateProvider taskStateProvider; /** diff --git a/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java b/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java index e27c888f5..c0297fa6f 100644 --- a/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java +++ b/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java @@ -16,8 +16,11 @@ public class InMemoryQueueManager implements QueueManager { private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryQueueManager.class); private final ConcurrentMap queues = new ConcurrentHashMap<>(); - private final EventQueueFactory factory; - private final TaskStateProvider taskStateProvider; + // Fields set by constructor injection cannot be final. We need a noargs constructor for + // Jakarta compatibility, and it seems that making fields set by constructor injection + // final, is not proxyable in all runtimes + private EventQueueFactory factory; + private TaskStateProvider taskStateProvider; /** * No-args constructor for CDI proxy creation. diff --git a/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java b/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java index 6f7ffcbe2..e19f7920d 100644 --- a/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java +++ b/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java @@ -56,7 +56,6 @@ import io.a2a.spec.Message; import io.a2a.spec.MessageSendParams; import io.a2a.spec.PushNotificationConfig; -import io.a2a.spec.PushNotificationNotSupportedError; import io.a2a.spec.StreamingEventKind; import io.a2a.spec.Task; import io.a2a.spec.TaskIdParams; @@ -208,17 +207,20 @@ public class DefaultRequestHandler implements RequestHandler { */ int consumptionCompletionTimeoutSeconds; - private final AgentExecutor agentExecutor; - private final TaskStore taskStore; - private final QueueManager queueManager; - private final PushNotificationConfigStore pushConfigStore; - private final PushNotificationSender pushSender; - private final Supplier requestContextBuilder; + // Fields set by constructor injection cannot be final. We need a noargs constructor for + // Jakarta compatibility, and it seems that making fields set by constructor injection + // final, is not proxyable in all runtimes + private AgentExecutor agentExecutor; + private TaskStore taskStore; + private QueueManager queueManager; + private PushNotificationConfigStore pushConfigStore; + private PushNotificationSender pushSender; + private Supplier requestContextBuilder; private final ConcurrentMap> runningAgents = new ConcurrentHashMap<>(); private final Set> backgroundTasks = ConcurrentHashMap.newKeySet(); - private final Executor executor; + private Executor executor; /** * No-args constructor for CDI proxy creation. diff --git a/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java b/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java index 388284089..06926ee55 100644 --- a/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java +++ b/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java @@ -27,8 +27,11 @@ public class BasePushNotificationSender implements PushNotificationSender { private static final Logger LOGGER = LoggerFactory.getLogger(BasePushNotificationSender.class); - private final A2AHttpClient httpClient; - private final PushNotificationConfigStore configStore; + // Fields set by constructor injection cannot be final. We need a noargs constructor for + // Jakarta compatibility, and it seems that making fields set by constructor injection + // final, is not proxyable in all runtimes + private A2AHttpClient httpClient; + private PushNotificationConfigStore configStore; /** * No-args constructor for CDI proxy creation. diff --git a/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java b/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java index 7a1e0a912..2998965ea 100644 --- a/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java +++ b/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java @@ -67,7 +67,7 @@ public abstract class GrpcHandler extends A2AServiceGrpc.A2AServiceImplBase { // Without this we get intermittent failures private static volatile Runnable streamingSubscribedRunnable; - private AtomicBoolean initialised = new AtomicBoolean(false); + private final AtomicBoolean initialised = new AtomicBoolean(false); private static final Logger LOGGER = Logger.getLogger(GrpcHandler.class.getName()); diff --git a/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java b/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java index 1410aa6c8..7a0fdc58c 100644 --- a/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java +++ b/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java @@ -60,10 +60,13 @@ @ApplicationScoped public class JSONRPCHandler { + // Fields set by constructor injection cannot be final. We need a noargs constructor for + // Jakarta compatibility, and it seems that making fields set by constructor injection + // final, is not proxyable in all runtimes private AgentCard agentCard; private @Nullable Instance extendedAgentCard; private RequestHandler requestHandler; - private final Executor executor; + private Executor executor; @SuppressWarnings("NullAway") protected JSONRPCHandler() { diff --git a/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java b/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java index 8c4050fd9..074cefea4 100644 --- a/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java +++ b/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java @@ -67,10 +67,14 @@ public class RestHandler { private static final Logger log = Logger.getLogger(RestHandler.class.getName()); + + // Fields set by constructor injection cannot be final. We need a noargs constructor for + // Jakarta compatibility, and it seems that making fields set by constructor injection + // final, is not proxyable in all runtimes private AgentCard agentCard; private @Nullable Instance extendedAgentCard; private RequestHandler requestHandler; - private final Executor executor; + private Executor executor; @SuppressWarnings("NullAway") protected RestHandler() { From 6e1c6000d78463e4b70336fadbc8fdbe58eb2cd9 Mon Sep 17 00:00:00 2001 From: Kabir Khan Date: Fri, 9 Jan 2026 16:52:36 +0000 Subject: [PATCH 3/3] Satisfy my Gemini overlord --- .../queuemanager/replicated/core/ReplicatedQueueManager.java | 2 ++ .../main/java/io/a2a/server/events/InMemoryQueueManager.java | 2 ++ .../java/io/a2a/server/tasks/BasePushNotificationSender.java | 3 +++ .../io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java | 5 +++++ .../main/java/io/a2a/transport/rest/handler/RestHandler.java | 5 +++++ 5 files changed, 17 insertions(+) diff --git a/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java b/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java index ffc06783c..586ab11a7 100644 --- a/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java +++ b/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java @@ -33,6 +33,8 @@ public class ReplicatedQueueManager implements QueueManager { /** * No-args constructor for CDI proxy creation. + * CDI requires a non-private constructor to create proxies for @ApplicationScoped beans. + * All fields are initialized by the @Inject constructor during actual bean creation. */ @SuppressWarnings("NullAway") protected ReplicatedQueueManager() { diff --git a/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java b/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java index c0297fa6f..e5a17e0e7 100644 --- a/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java +++ b/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java @@ -24,6 +24,8 @@ public class InMemoryQueueManager implements QueueManager { /** * No-args constructor for CDI proxy creation. + * CDI requires a non-private constructor to create proxies for @ApplicationScoped beans. + * All fields are initialized by the @Inject constructor during actual bean creation. */ @SuppressWarnings("NullAway") protected InMemoryQueueManager() { diff --git a/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java b/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java index 06926ee55..a59767d8f 100644 --- a/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java +++ b/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java @@ -33,8 +33,11 @@ public class BasePushNotificationSender implements PushNotificationSender { private A2AHttpClient httpClient; private PushNotificationConfigStore configStore; + /** * No-args constructor for CDI proxy creation. + * CDI requires a non-private constructor to create proxies for @ApplicationScoped beans. + * All fields are initialized by the @Inject constructor during actual bean creation. */ @SuppressWarnings("NullAway") protected BasePushNotificationSender() { diff --git a/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java b/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java index 7a0fdc58c..ca8149099 100644 --- a/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java +++ b/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java @@ -68,6 +68,11 @@ public class JSONRPCHandler { private RequestHandler requestHandler; private Executor executor; + /** + * No-args constructor for CDI proxy creation. + * CDI requires a non-private constructor to create proxies for @ApplicationScoped beans. + * All fields are initialized by the @Inject constructor during actual bean creation. + */ @SuppressWarnings("NullAway") protected JSONRPCHandler() { // For CDI proxy creation diff --git a/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java b/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java index 074cefea4..489bf909b 100644 --- a/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java +++ b/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java @@ -76,6 +76,11 @@ public class RestHandler { private RequestHandler requestHandler; private Executor executor; + /** + * No-args constructor for CDI proxy creation. + * CDI requires a non-private constructor to create proxies for @ApplicationScoped beans. + * All fields are initialized by the @Inject constructor during actual bean creation. + */ @SuppressWarnings("NullAway") protected RestHandler() { // For CDI