grpc/grpc-java · error · IllegalArgumentException

Duration is not valid. See proto definition for valid values

Error message

Duration is not valid. See proto definition for valid values. Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]. Nanos (%s) must be in range [-999,999,999, +999,999,999]. Nanos must have the same sign as seconds

What it means

JsonUtil.normalizedDuration converts a proto JSON Duration (seconds + nanos) into a single nanoseconds long. Before combining, it validates that seconds fit within the proto Duration range (±315,576,000,000) and nanos within ±999,999,999 with matching sign. If durationIsValid fails, it throws this IllegalArgumentException because the resulting value cannot be represented as a valid proto duration.

Source

Thrown at core/src/main/java/io/grpc/internal/JsonUtil.java:353

   * Copy of {@link com.google.protobuf.util.Durations#normalizedDuration}.
   */
  // Math.addExact() requires Android API level 24
  @SuppressWarnings({"NarrowingCompoundAssignment", "InlineMeInliner"})
  private static long normalizedDuration(long seconds, int nanos) {
    if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
      seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND);
      nanos %= NANOS_PER_SECOND;
    }
    if (seconds > 0 && nanos < 0) {
      nanos += NANOS_PER_SECOND; // no overflow— nanos is negative (and we're adding)
      seconds--; // no overflow since seconds is positive (and we're decrementing)
    }
    if (seconds < 0 && nanos > 0) {
      nanos -= NANOS_PER_SECOND; // no overflow— nanos is positive (and we're subtracting)
      seconds++; // no overflow since seconds is negative (and we're incrementing)
    }
    if (!durationIsValid(seconds, nanos)) {
      throw new IllegalArgumentException(String.format(
          "Duration is not valid. See proto definition for valid values. "
              + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]. "
              + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. "
              + "Nanos must have the same sign as seconds", seconds, nanos));
    }
    return saturatedAdd(TimeUnit.SECONDS.toNanos(seconds), nanos);
  }

  /**
   * Returns true if the given number of seconds and nanos is a valid {@code Duration}. The {@code
   * seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The {@code nanos}
   * value must be in the range [-999,999,999, +999,999,999].
   *
   * <p><b>Note:</b> Durations less than one second are represented with a 0 {@code seconds} field
   * and a positive or negative {@code nanos} field. For durations of one second or more, a non-zero
   * value for the {@code nanos} field must be of the same sign as the {@code seconds} field.
   *
   * <p>Copy of {@link com.google.protobuf.util.Duration#isValid}.</p>

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Fix the duration string/value in the config so seconds is within ±315,576,000,000 and nanos within ±999,999,999 with the same sign (e.g. '5s' not '5s-500ns').
  2. Parse with a standard format like '3.000000001s' (protobuf JSON duration syntax) instead of hand-assembling seconds/nanos.
  3. If computing durations programmatically, clamp or saturate values before passing them into JSON parsing.

Example fix

// before
{"methodConfig": [{"timeout": "99999999999999s"}]}
// after
{"methodConfig": [{"timeout": "30s"}]}
Defensive patterns

Strategy: validation

Validate before calling

boolean validDuration(String d) {
  java.util.regex.Matcher m = java.util.regex.Pattern.compile("^(-?\\d+)(?:\\.(\\d{1,9}))?s$").matcher(d);
  if (!m.matches()) return false;
  long sec = Long.parseLong(m.group(1));
  int nanos = m.group(2) == null ? 0 : Integer.parseInt((m.group(2) + "000000000").substring(0, 9)) * (sec < 0 ? -1 : 1);
  return Math.abs(sec) <= 315576000000L && Math.abs(nanos) <= 999999999;
}
// use: if (!validDuration(timeoutStr)) throw new IllegalArgumentException("bad timeout: " + timeoutStr);

Try / catch

try {
  parseConfig(json);
} catch (IllegalArgumentException e) {
  if (e.getMessage() != null && e.getMessage().contains("Duration is not valid")) {
    log.warn("Invalid duration in config, using default");
  } else throw e;
}

Prevention

When it happens

Trigger: Parsing a JSON/proto Duration via parseDuration where the input has seconds outside ±315,576,000,000, nanos outside ±999,999,999, or nanos with a sign opposite to seconds after normalization (e.g. seconds=5 and nanos=-500 when |seconds| >= the range limit).

Common situations: Hand-written channel/service config JSON with a malformed duration string like '99999999999999999s', or durations constructed programmatically with mismatched sign components; also duration strings exceeding ~10,000 years.

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/3103f845dfec79c9. Report an issue: GitHub.