grpc/grpc-java · error · IllegalArgumentException

Invalid timeout unit

Error message

Invalid timeout unit: %s

What it means

gRPC timeout header values are numeric amounts followed by a unit character (n/u/m/S/M/H). GrpcUtil's timeout parser converts these to nanoseconds, but if the trailing unit character is not one of the supported units, it throws IllegalArgumentException("Invalid timeout unit: %s").

Solutions

  1. Emit gRPC-conformant timeouts with exactly one of the unit suffixes: n (ns), u (µs), m (ms), S, M, or H - e.g. "100m" not "100ms".
  2. Produce the header via Deadline/Deadline.Ticker APIs (e.g. deadline.timeRemaining(TimeUnit.NANOSECONDS) with a unit suffix) instead of manual formatting.
  3. Check the component that writes the timeout header (proxy, interceptor, custom marshaller) and fix its unit mapping.

Example fix

// before
String timeout = duration.toMillis() + "ms";
// after
String timeout = duration.toMillis() + "m"; // gRPC unit for millis
Defensive patterns

Strategy: validation

Validate before calling

String s = timeoutString;
if (!s.matches("^\\d+[nSumMH]$")) throw new IllegalArgumentException("Invalid grpc-timeout: " + s);

Try / catch

try {
  long nanos = GrpcUtil.TIMER_SERVICE.decodeTimeout(value);
} catch (IllegalArgumentException e) {
  // apply default deadline
}

Prevention

When it happens

Trigger: Sending or receiving a grpc-timeout value whose unit suffix is not n, u, m, S, M, or H - e.g. '100ms', '5s' (lowercase seconds), or a bare number without a unit - when the marshaller parses the ASCII string.

Common situations: Custom clients/proxies emitting lowercase unit letters or ISO-style 'ms' units; intermediary middleware rewriting the timeout header incorrectly; hand-rolled Deadline encoding in tests.

Understand the failure class

Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/6418fb9b6f5ad1cc. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/io/grpc/internal/GrpcUtil.java:702

      checkArgument(serialized.length() > 0, "empty timeout");
      checkArgument(serialized.length() <= 9, "bad timeout format");
      long value = Long.parseLong(serialized.substring(0, serialized.length() - 1));
      char unit = serialized.charAt(serialized.length() - 1);
      switch (unit) {
        case 'n':
          return value;
        case 'u':
          return TimeUnit.MICROSECONDS.toNanos(value);
        case 'm':
          return TimeUnit.MILLISECONDS.toNanos(value);
        case 'S':
          return TimeUnit.SECONDS.toNanos(value);
        case 'M':
          return TimeUnit.MINUTES.toNanos(value);
        case 'H':
          return TimeUnit.HOURS.toNanos(value);
        default:
          throw new IllegalArgumentException(String.format("Invalid timeout unit: %s", unit));
      }
    }
  }

  /**
   * Returns a transport out of a PickResult, or {@code null} if the result is "buffer".
   */
  @Nullable
  static ClientTransport getTransportFromPickResult(PickResult result, boolean isWaitForReady) {
    final ClientTransport transport;
    Subchannel subchannel = result.getSubchannel();
    if (subchannel != null) {
      transport = ((TransportProvider) subchannel.getInternalSubchannel()).obtainActiveTransport();
    } else {
      transport = null;
    }
    if (transport != null) {
      final ClientStreamTracer.Factory streamTracerFactory = result.getStreamTracerFactory();

View on GitHub (pinned to 64daddc1f3)