Skip to content

Service Configuration containing Integer values abort with IllegalArgumentException #12815

@ludgerb

Description

@ludgerb

What version of gRPC-Java are you using?

grpc-core_1.75.0
the issue also exists in grpc-core_1.81.0

What is your environment?

Microsoft Windows 11; openjdk version "21.0.10" 2026-01-20 LTS

What did you expect to see?

When using a service configuration, some fields of the configuration are expected to be Numeric.
Expectation is, that I can use Integer values here.

Given the following service configuration with a retry policy taken "as is" from the official documentation https://grpc.io/docs/guides/retry/#retry-configuration:

{
  "methodConfig": [
    {
      "name": [{}],
      "retryPolicy": {
        "maxAttempts": 4,
        "initialBackoff": "0.1s",
        "maxBackoff": "1s",
        "backoffMultiplier": 2,
        "retryableStatusCodes": [
          "UNAVAILABLE"
        ]
      }
    }
  ]
}

This should be an accepted retry policy.
"maxAttempt": 4 and "backoffMultiplier": 2 are valid (integer) values according to the documentation.

What did you see instead?

Using this retry policy example taken from the official grpc documentation fails to be parsed with the following Exception:
java.lang.IllegalArgumentException: The value of the map entry 'maxAttempts=4' is of type 'class java.lang.Integer', which is not supported.

grpc-java does only accepts Double-values.
The following service configuration is accepted though:

{
  "methodConfig": [
    {
      "name": [{}],
      "retryPolicy": {
        "maxAttempts": 4.0,
        "initialBackoff": "0.1s",
        "maxBackoff": "1s",
        "backoffMultiplier": 2.0,
        "retryableStatusCodes": [
          "UNAVAILABLE"
        ]
      }
    }
  ]
}

Please note that the integers 4 and 2 are replaced with Double values 4.0 and 2.0

Steps to reproduce the bug

Code used to create the ManagedChannel:

  @Autowired private ObjectMapper objectMapper;

  private static final String GRPC_SERVICE_CONFIGURATION =
      """
      {
        "methodConfig": [
          {
            "name": [{}],
            "retryPolicy": {
              "maxAttempts": 4,
              "initialBackoff": "0.1s",
              "maxBackoff": "1s",
              "backoffMultiplier": 2,
              "retryableStatusCodes": [
                "UNAVAILABLE"
              ]
            }
          }
        ]
      }""";

  private Map<String, ?> getGrpcServiceConfiguration() throws IOException {
    return objectMapper.readValue(GRPC_SERVICE_CONFIGURATION, new TypeReference<>() {});
  }

  public ManagedChannel buildManagedChannel(ChannelKey channelKey) throws IOException {
    return ManagedChannelBuilder.forAddress(channelKey.host, channelKey.port)
        .usePlaintext()
        .defaultServiceConfig(getGrpcServiceConfiguration()) // <== throws unexpected IllegalArgumentException
        .enableRetry()
        .build();
  }

Error Message & StackTrace:

java.lang.IllegalArgumentException: The value of the map entry 'maxAttempts=3' is of type 'class java.lang.Integer', which is not supported

	at io.grpc.internal.ManagedChannelImplBuilder.checkMapEntryTypes(ManagedChannelImplBuilder.java:588)
	at io.grpc.internal.ManagedChannelImplBuilder.checkMapEntryTypes(ManagedChannelImplBuilder.java:577)
	at io.grpc.internal.ManagedChannelImplBuilder.checkListEntryTypes(ManagedChannelImplBuilder.java:601)
	at io.grpc.internal.ManagedChannelImplBuilder.checkMapEntryTypes(ManagedChannelImplBuilder.java:579)
	at io.grpc.internal.ManagedChannelImplBuilder.defaultServiceConfig(ManagedChannelImplBuilder.java:556)
	at io.grpc.internal.ManagedChannelImplBuilder.defaultServiceConfig(ManagedChannelImplBuilder.java:72)
	at io.grpc.ForwardingChannelBuilder2.defaultServiceConfig(ForwardingChannelBuilder2.java:250)

The Integer Type is rejected in ManagedChannelImplBuilder.checkMapEntryTypes:

https://github.com/grpc/grpc-java/blob/v1.75.x/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java#L582-L590:

  private static List<?> checkListEntryTypes(List<?> list) {
    List<Object> parsedList = new ArrayList<>(list.size());
    for (Object value : list) {
      if (value == null) {
        parsedList.add(null);
      } else if (value instanceof Map) {
        parsedList.add(checkMapEntryTypes((Map<?, ?>) value));
      } else if (value instanceof List) {
        parsedList.add(checkListEntryTypes((List<?>) value));
      } else if (value instanceof String) {
        parsedList.add(value);
      } else if (value instanceof Double) {  // <==== only accepts numerics of type Double (should accept Number instead imo)
        parsedList.add(value);
      } else if (value instanceof Boolean) {
        parsedList.add(value);
      } else {
        throw new IllegalArgumentException(
            "The entry '" + value + "' is of type '" + value.getClass()
                + "', which is not supported");
      }
    }
    return Collections.unmodifiableList(parsedList);
  }

Imo, instead of checking for java.lang.Double, grpc-java should accept any instance of type java.lang.Number.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions