What happened?
A2ACommonFieldMapper.structToMap throws NullPointerException when a protobuf Struct contains a NULL_VALUE field. That is the mapping for a JSON null inside a DataPart's data or any metadata object.
valueToObject returns Java null for NULL_VALUE. Collectors.toMap then throws, because it does not accept null values. This is documented Collectors.toMap behaviour, not a missing null-check in the caller.
JSON-RPC message/send with a data part like {"attributeId": null, "source": "ID"} never produces a domain Message. The parse dies inside JSONRPCUtils.parseRequestBody.
Seen on org.a2aproject.sdk:a2a-java-sdk-spec-grpc:1.1.0.Final. The same Collectors.toMap call is still on main in spec-grpc/.../A2ACommonFieldMapper.java.
Related but different: #618 is an empty nested Struct (structToMap returns null for zero fields, then the parent toMap NPEs). This report is a JSON null value (NULL_VALUE). Fixing empty structs to emptyMap() does not fix this path.
Expected: JSON null in data / metadata becomes a Java null in the resulting Map, and the key is kept. containsKey("attributeId") is true and get("attributeId") is null.
Suggested fix: populate a HashMap with forEach instead of Collectors.toMap. Also stop returning null for an empty struct (return emptyMap()), which is the same null-hostile habit as #618.
default Map<String, Object> structToMap(Struct struct) {
if (struct == null || struct.getFieldsCount() == 0) {
return Collections.emptyMap();
}
Map<String, Object> map = new HashMap<>();
struct.getFieldsMap().forEach((k, v) -> map.put(k, valueToObject(v)));
return map;
}
Note that Message / Part constructors still use Map.copyOf on metadata, which forbids top-level null values. Even after this mapper fix, rebuilding a Message with null metadata values will NPE unless those constructors use a null-tolerant copy.
Relevant log output
java.lang.NullPointerException
at java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$0(Collectors.java:180)
at org.a2aproject.sdk.grpc.mapper.A2ACommonFieldMapper.structToMap(A2ACommonFieldMapper.java:170)
at org.a2aproject.sdk.grpc.mapper.PartMapper.fromProto(PartMapper.java:110)
at org.a2aproject.sdk.grpc.mapper.MessageMapperImpl.fromProto(MessageMapperImpl.java:72)
at org.a2aproject.sdk.grpc.utils.JSONRPCUtils.parseRequestBody(JSONRPCUtils.java:192)
Code of Conduct
What happened?
A2ACommonFieldMapper.structToMapthrowsNullPointerExceptionwhen a protobufStructcontains aNULL_VALUEfield. That is the mapping for a JSONnullinside aDataPart'sdataor anymetadataobject.valueToObjectreturns JavanullforNULL_VALUE.Collectors.toMapthen throws, because it does not accept null values. This is documentedCollectors.toMapbehaviour, not a missing null-check in the caller.JSON-RPC
message/sendwith a data part like{"attributeId": null, "source": "ID"}never produces a domainMessage. The parse dies insideJSONRPCUtils.parseRequestBody.Seen on
org.a2aproject.sdk:a2a-java-sdk-spec-grpc:1.1.0.Final. The sameCollectors.toMapcall is still onmaininspec-grpc/.../A2ACommonFieldMapper.java.Related but different: #618 is an empty nested
Struct(structToMapreturnsnullfor zero fields, then the parenttoMapNPEs). This report is a JSONnullvalue (NULL_VALUE). Fixing empty structs toemptyMap()does not fix this path.Expected: JSON
nullindata/metadatabecomes a Javanullin the resultingMap, and the key is kept.containsKey("attributeId")is true andget("attributeId")is null.Suggested fix: populate a
HashMapwithforEachinstead ofCollectors.toMap. Also stop returningnullfor an empty struct (returnemptyMap()), which is the same null-hostile habit as #618.Note that
Message/Partconstructors still useMap.copyOfon metadata, which forbids top-level null values. Even after this mapper fix, rebuilding aMessagewith null metadata values will NPE unless those constructors use a null-tolerant copy.Relevant log output
Code of Conduct