Is your feature request related to a problem? Please describe.
According to the A2A Spec:
Rate Limiting and Abuse Prevention:
- Agents SHOULD implement rate limiting on all operations
- Agents SHOULD return appropriate error responses when rate limits are exceeded
- Agents MAY implement different rate limits for different operations or user tiers
We noticed that the org.a2aproject.sdk.client.http.A2AHttpResponse does not expose response headers in the interface. This matters because if the server returns a 429 with a Retry-After header, it would be nice to access that so the client consumer can know how long to wait for a retry.
Currently the org.a2aproject.sdk.client.http.JdkA2AHttpClient.JdkPostBuilder does not check for a 429, which is one limiting factor:
if (response.statusCode() == HTTP_UNAUTHORIZED) {
throw new IOException(A2AErrorMessages.AUTHENTICATION_FAILED);
} else if (response.statusCode() == HTTP_FORBIDDEN) {
throw new IOException(A2AErrorMessages.AUTHORIZATION_FAILED);
}
...But we have our own implementation of org.a2aproject.sdk.client.http.A2AHttpClient with a custom PostBuilder where we could catch the 429. Nevertheless- the A2AHttpResponse doesn't expose headers for us to read the value from. Our only option is to short-circuit throwing our own exception with the header info captured.
Ideally, the client SDK would expose response headers — at minimum for error responses. Retry-After (429/503) and WWW-Authenticate (401) are both examples where the header carries information that a client consumer needs to act on programmatically.
Describe the solution you'd like
Extend org.a2aproject.sdk.client.http.A2AHttpResponse to expose response headers and ensure they are passed on to the org.a2aproject.sdk.spec.A2AClientHTTPError
Describe alternatives you've considered
Our alternative today is to short-circuit the client SDK exception handling by grabbing the response header ourselves in the 429 case:
public class CustomA2AHttpClient implements A2AHttpClient {
private class CustomPostBuilder implements PostBuilder {
// ...
@Override
public A2AHttpResponse post() throws IOException, InterruptedException {
HttpResponse<byte[]> raw = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
// Workaround: intercept 429 before the SDK wraps it as A2AClientHTTPError,
// because A2AHttpResponse doesn't expose headers and the Retry-After value is lost.
if (raw.statusCode() == 429) {
String retryAfter = raw.headers().firstValue("Retry-After").orElse(null);
throw new IOException("Rate limited (Retry-After: " + retryAfter + ")");
// We lose the structured Retry-After value here — our only option is to
// encode it in the exception message or throw a custom exception type.
}
return toA2AHttpResponse(raw);
}
}
}
Additional context
Note on our current workaround:
We intercept the 429 in our A2AHttpClient.PostBuilder.post() implementation by throwing a RuntimeException subclass before returning A2AHttpResponse. This works today because the SDK's JSONRPCTransport.sendPostRequest() only catches IOException and InterruptedException — unchecked exceptions propagate through unmodified.
However, this is fragile. If a future SDK version adds a broader catch (e.g. catch (Exception e) or catch (RuntimeException e)) around the builder.post() call — perhaps to wrap transport errors in A2AClientException — our exception would be swallowed or re-wrapped, silently breaking the Retry-After propagation.
We have an integration test that checks for this potential future case, but the underlying issue remains: we're relying on an implicit contract (unchecked exceptions pass through) rather than an explicit API.
A proper A2AHttpResponse.headers() method would eliminate this workaround entirely.
Code of Conduct
Is your feature request related to a problem? Please describe.
According to the A2A Spec:
We noticed that the
org.a2aproject.sdk.client.http.A2AHttpResponsedoes not expose response headers in the interface. This matters because if the server returns a 429 with aRetry-Afterheader, it would be nice to access that so the client consumer can know how long to wait for a retry.Currently the
org.a2aproject.sdk.client.http.JdkA2AHttpClient.JdkPostBuilderdoes not check for a 429, which is one limiting factor:...But we have our own implementation of
org.a2aproject.sdk.client.http.A2AHttpClientwith a customPostBuilderwhere we could catch the 429. Nevertheless- theA2AHttpResponsedoesn't expose headers for us to read the value from. Our only option is to short-circuit throwing our own exception with the header info captured.Ideally, the client SDK would expose response headers — at minimum for error responses. Retry-After (429/503) and WWW-Authenticate (401) are both examples where the header carries information that a client consumer needs to act on programmatically.
Describe the solution you'd like
Extend
org.a2aproject.sdk.client.http.A2AHttpResponseto expose response headers and ensure they are passed on to theorg.a2aproject.sdk.spec.A2AClientHTTPErrorDescribe alternatives you've considered
Our alternative today is to short-circuit the client SDK exception handling by grabbing the response header ourselves in the 429 case:
Additional context
Note on our current workaround:
We intercept the 429 in our
A2AHttpClient.PostBuilder.post()implementation by throwing aRuntimeExceptionsubclass before returningA2AHttpResponse. This works today because the SDK'sJSONRPCTransport.sendPostRequest()only catchesIOExceptionandInterruptedException— unchecked exceptions propagate through unmodified.However, this is fragile. If a future SDK version adds a broader catch (e.g.
catch (Exception e)orcatch (RuntimeException e)) around thebuilder.post()call — perhaps to wrap transport errors inA2AClientException— our exception would be swallowed or re-wrapped, silently breaking the Retry-After propagation.We have an integration test that checks for this potential future case, but the underlying issue remains: we're relying on an implicit contract (unchecked exceptions pass through) rather than an explicit API.
A proper
A2AHttpResponse.headers()method would eliminate this workaround entirely.Code of Conduct