grpc/grpc-go · error
malformed duration : too many digits after decimal
Error message
malformed duration %q: too many digits after decimal
What it means
Fires at duration.go:96 when the fractional (sub-second) part of a protobuf Duration JSON string has more than 9 digits. The protobuf Duration type only carries nanosecond precision, so more than 9 fractional digits cannot be represented and is rejected rather than silently truncated.
Solutions
- Trim the fractional part of the offending value to at most 9 digits.
- If you control the producer, format with %09d on the nanosecond component (the library's own MarshalJSON trims trailing zeros to 0/3/6/9 digits).
- Round the source time.Duration to nanosecond precision before serializing.
Example fix
// before // "timeout": "1.1234567890s" // 10 fractional digits // after // "timeout": "1.123456789s" // 9 digits max (nanosecond precision)
Defensive patterns
Strategy: validation
Validate before calling
func formatDuration(d time.Duration) string {
// Trim to <= 9 fractional digits; library MarshalJSON already does this.
b, _ := json.Marshal(time.Duration(d)) // use canonical encoding
return string(b)
} Try / catch
if err := json.Unmarshal(rawConfig, &cfg); err != nil {
if strings.Contains(err.Error(), "too many digits after decimal") {
// trim the fractional part to 9 digits and retry
}
} Prevention
- Never format fractional seconds with more than 9 digits; round to nanoseconds.
- Use %09d on the nanosecond component, then trim trailing zeros, matching the library.
When it happens
Trigger: A Duration string whose decimal portion (ss[1] after splitting on '.') has length > 9. Examples: "1.1234567890s" (10 digits), "0.0000000001s", or a value formatted by code that pads fractions to fixed widths greater than 9.
Common situations: A serializer that emits microsecond timestamps as Duration strings with 12-15 fractional digits; config generated from time.Duration.Nanoseconds() formatting with too many digits; values copied from a system that uses picosecond/femtosecond precision.
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 : contains no numbers
- malformed duration
- out of range
- duplicated name
- error parsing custom audit logger config
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/e2a33d383c3cd8de.
Report an issue: GitHub.
Appendix: 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 0c51461d27)