grpc/grpc-go · error
malformed duration %q: too many digits after decimal
Error message
malformed duration %q: too many digits after decimal
What it means
Returned by Duration.UnmarshalJSON when the fractional (post-decimal) portion of the duration string has more than 9 digits. Nanosecond precision is the maximum the protobuf Duration spec and Go's time.Duration support, so additional digits are rejected rather than silently truncated.
Source
Thrown at internal/serviceconfig/duration.go:96
// hasDigits is set if either the whole or fractional part of the number is
// present, since both are optional but one is required.
hasDigits := false
var sec, ns int64
if len(ss[0]) > 0 {
var err error
if sec, err = strconv.ParseInt(ss[0], 10, 64); err != nil {
return fmt.Errorf("malformed duration %q: %v", s, err)
}
// Maximum seconds value per the durationpb spec.
const maxProtoSeconds = 315_576_000_000
if sec > maxProtoSeconds {
return fmt.Errorf("out of range: %q", s)
}
hasDigits = true
}
if len(ss) == 2 && len(ss[1]) > 0 {
if len(ss[1]) > 9 {
return fmt.Errorf("malformed duration %q: too many digits after decimal", s)
}
var err error
if ns, err = strconv.ParseInt(ss[1], 10, 64); err != nil {
return fmt.Errorf("malformed duration %q: %v", s, err)
}
for i := 9; i > len(ss[1]); i-- {
ns *= 10
}
hasDigits = true
}
if !hasDigits {
return fmt.Errorf("malformed duration %q: contains no numbers", s)
}
if neg {
sec *= -1
ns *= -1
}View on GitHub (pinned to 03255a9237)
Solutions
- Trim the fractional part to at most 9 digits, e.g. "1.123456789s".
- Round the source value to nanosecond precision before serializing.
- Generate durations via serviceconfig.Duration.MarshalJSON which always emits 3/6/9 digits.
Example fix
// before
{"timeout": "0.1234567890s"}
// after
{"timeout": "0.123456789s"} Defensive patterns
Strategy: validation
Validate before calling
func validateProtoDuration(s string) error {
body := strings.TrimSuffix(s, "s")
parts := strings.SplitN(body, ".", 2)
if len(parts) == 2 && len(parts[1]) > 9 {
return fmt.Errorf("too many fractional digits in %q", s)
}
var d serviceconfig.Duration
return d.UnmarshalJSON([]byte(`"` + s + `"`))
} Type guard
func fractionalDigitsAtMost9(s string) bool {
body := strings.TrimSuffix(s, "s")
parts := strings.SplitN(body, ".", 2)
return len(parts) != 2 || len(parts[1]) <= 9
} Prevention
- Round source values to nanosecond precision before serializing to protobuf JSON.
- Generate durations through serviceconfig.Duration.MarshalJSON to guarantee ≤9 digits.
- Lint config for over-precise fractional values.
When it happens
Trigger: A value like "1.1234567890s" (10 fractional digits) triggers `if len(ss[1]) > 9` at duration.go:95. Typical when a high-precision timestamp or arbitrary float is written as a duration.
Common situations: Tooling that emits full float64 precision; copy/paste of a nanosecond timestamp with sub-ns digits; localized formatting that pads decimals.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- malformed duration %q: too many decimals
- malformed duration %q: %v
- malformed duration %q: missing seconds unit
- out of range: %q
- malformed duration %q: contains no numbers
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/e2a33d383c3cd8de.
Report an issue: GitHub.