grpc/grpc-go · error
malformed duration %q: too many decimals
Error message
malformed duration %q: too many decimals
What it means
Returned by Duration.UnmarshalJSON when the duration string contains more than one decimal point. The number is split on '.' with a limit of 3; producing 3 parts (two dots) is invalid per the protobuf JSON Duration grammar, which permits at most one fractional component. Example: "1.2.3s".
Source
Thrown at internal/serviceconfig/duration.go:76
}
// UnmarshalJSON unmarshals b as a duration JSON string into d.
func (d *Duration) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
if !strings.HasSuffix(s, "s") {
return fmt.Errorf("malformed duration %q: missing seconds unit", s)
}
neg := false
if s[0] == '-' {
neg = true
s = s[1:]
}
ss := strings.SplitN(s[:len(s)-1], ".", 3)
if len(ss) > 2 {
return fmt.Errorf("malformed duration %q: too many decimals", s)
}
// 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 {View on GitHub (pinned to 03255a9237)
Solutions
- Rewrite the value with at most one decimal point, e.g. "1.5s".
- Generate durations via serviceconfig.Duration / protojson marshal rather than hand-editing strings.
- Lint duration fields for stray dots before applying service config.
Example fix
// before
{"timeout": "1.5.5s"}
// after
{"timeout": "1.5s"} Defensive patterns
Strategy: validation
Validate before calling
func validateProtoDuration(s string) error {
body := strings.TrimSuffix(s, "s")
if strings.Count(body, ".") > 1 {
return fmt.Errorf("too many decimals in %q", s)
}
var d serviceconfig.Duration
return d.UnmarshalJSON([]byte(`"` + s + `"`))
} Type guard
func hasAtMostOneDecimalPoint(s string) bool {
return strings.Count(strings.TrimSuffix(s, "s"), ".") <= 1
} Prevention
- Generate durations via serviceconfig.Duration marshaling rather than hand-editing.
- Lint config duration fields for stray dots in CI.
When it happens
Trigger: Passing a config value like "1.5.5s" or any duration string with multiple '.' separators. The check is `if len(ss) > 2` after strings.SplitN(s, ".", 3) at duration.go:74.
Common situations: Hand-built JSON config with a typo; templating that concatenated values producing an extra dot; localized number formatting leaking into config.
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: %v
- malformed duration %q: too many digits after decimal
- 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/6c06fe79dd6fff6a.
Report an issue: GitHub.