Skip to content

[Bug]: NullPointerException in A2ACommonFieldMapper.valueToObject when processing empty Struct #618

@lizongbo

Description

@lizongbo

What happened?

Description

The valueToObject method in A2ACommonFieldMapper throws a NullPointerException when processing a protobuf Value that contains an empty Struct (i.e., a struct with no fields).

Root Cause

In the valueToObject method, when handling STRUCT_VALUE case, it directly calls structToMap(value.getStructValue()). However, the structToMap method returns null when the struct is empty (has 0 fields):

default Map<String, Object> structToMap(Struct struct) {
    if (struct == null || struct.getFieldsCount() == 0) {
        return null;  // Returns null for empty struct
    }
    // ...
}

This causes issues when the result is used in contexts that expect a non-null Map, such as when collecting stream results into a Map.

Stack Trace

java.lang.NullPointerException
        at java.base/java.util.Objects.requireNonNull(Objects.java:233)
        at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180)
        at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
        at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet.lambda$entryConsumer$0(Collections.java:1778)
        at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
        at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
        at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntrySetSpliterator.forEachRemaining(Collections.java:1803)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
        at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
        at io.a2a.grpc.mapper.A2ACommonFieldMapper.structToMap(A2ACommonFieldMapper.java:185)
        at io.a2a.grpc.mapper.DataPartMapperImpl.fromProto(DataPartMapperImpl.java:39)
        at io.a2a.grpc.mapper.PartMapper.fromProto(PartMapper.java:64)
        at io.a2a.grpc.mapper.MessageMapperImpl.partListToPartArray(MessageMapperImpl.java:88)
        at io.a2a.grpc.mapper.MessageMapperImpl.fromProto(MessageMapperImpl.java:72)
        at io.a2a.grpc.mapper.MessageSendParamsMapperImpl.fromProto(MessageSendParamsMapperImpl.java:45)
        at io.a2a.grpc.utils.ProtoUtils$FromProto.lambda$4(ProtoUtils.java:231)
        at io.a2a.grpc.utils.ProtoUtils$FromProto.convert(ProtoUtils.java:193)
        at io.a2a.grpc.utils.ProtoUtils$FromProto.messageSendParams(ProtoUtils.java:231)

Steps to Reproduce

  1. Create a JSON message containing an empty struct, for example:
{
  "message": {
    "messageId": "test-id",
    "role": "ROLE_USER",
    "parts": [{
      "data": {
        "data": {
          "response": {
          },
          "id": "call_id",
          "name": "transfer_to_agent"
        }
      }
    }]
  }
}
  1. Parse this JSON into a protobuf message and convert it using ProtoUtils.FromProto.messageSendParams()

  2. A NullPointerException is thrown during the conversion

Expected Behavior

The method should return an empty Map instead of null when encountering an empty struct, allowing the conversion to complete successfully.

Proposed Fix

Add a null/empty check in the STRUCT_VALUE case of valueToObject method:

private Object valueToObject(Value value) {
    switch (value.getKindCase()) {
        case STRUCT_VALUE:
            if (value.getStructValue() == null || value.getStructValue().getFieldsCount() < 1) {
                return new java.util.HashMap<String, Object>();
            }
            return structToMap(value.getStructValue());
        // ... other cases remain unchanged
    }
}

Affected File

  • spec-grpc/src/main/java/io/a2a/grpc/mapper/A2ACommonFieldMapper.java

Environment

  • a2a-java version: 1.0.0.Alpha1 (or latest main branch)
  • Java version: 17+

Relevant log output

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions