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
- 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').
- Parse with a standard format like '3.000000001s' (protobuf JSON duration syntax) instead of hand-assembling seconds/nanos.
- 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
- Always emit durations in protobuf JSON form like '30s' or '1.500s'.
- Keep nanos and seconds the same sign; prefer a single signed string.
- Bound durations to realistic values (< 315,576,000,000 seconds).
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
- "key" is absent or empty
- "values" is absent or empty
- rule "name" is absent or empty
- "name" is absent or empty
- "allow_rules" is absent
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/3103f845dfec79c9.
Report an issue: GitHub.