grpc/grpc-go · error
malformed duration : missing seconds unit
Error message
malformed duration %q: missing seconds unit
What it means
serviceconfig.Duration.UnmarshalJSON (duration.go:60-68) parses a protobuf-JSON duration string, which must end with a single 's' (seconds unit), e.g. "1.5s", "0.250s", "-3s". If the suffix is missing, the string is rejected. This type backs gRPC service-config duration fields (timeouts, hedging, retry backoff).
Solutions
- Express durations in protobuf-JSON form with a trailing 's': "0.500s", "3s", "1.5s".
- Convert from Go time strings programmatically before serializing: use time.ParseDuration then format as seconds with up to 9 fractional digits + 's'.
- Validate config files with a JSON schema that enforces the '^-?\d+(\.\d{1,9})?s$' shape.
Example fix
// before (service config JSON):
// { "retryPolicy": { "initialBackoff": "500ms" } }
// // error: malformed duration "500ms": missing seconds unit
// after:
// { "retryPolicy": { "initialBackoff": "0.500s" } } Defensive patterns
Strategy: validation
Validate before calling
package main
import (
"fmt"
"regexp"
"time"
)
var durationRE = regexp.MustCompile(`^-?\d+(\.\d{1,9})?s$`)
func formatDuration(d time.Duration) string {
// Produce a protobuf-JSON duration string.
return time.Duration(d).String() // then convert below
}
func validateDurationJSON(s string) error {
if !durationRE.MatchString(s) {
return fmt.Errorf("duration %q must end with 's' and have <=9 fractional digits", s)
}
return nil
}
func _unused() { _ = formatDuration }
// func main() { _ = validateDurationJSON("0.5s") } Try / catch
// JSON unmarshal into serviceconfig.Duration returns this error.
//
// var d serviceconfig.Duration
// if err := json.Unmarshal([]byte(raw), &d); err != nil {
// if strings.Contains(err.Error(), "missing seconds unit") {
// // append the trailing 's' and retry, or surface to the user.
// }
// } Prevention
- Always use the 'NN.NNs' (seconds) form in service config JSON, not 'NNms'.
- Generate service config from typed structs and use Duration.MarshalJSON rather than hand-writing strings.
- Validate config files against a schema with regex '^-?\d+(\.\d{1,9})?s$'.
When it happens
Trigger: Triggered when JSON unmarshaling into a serviceconfig.Duration (or any struct embedding it) receives a string without a trailing 's'. Examples: "100ms", "5", "1.5" — none end in 's'.
Common situations: Hand-written service config JSON uses Go time-string syntax ("500ms") instead of protobuf-JSON syntax ("0.500s"), a config field is set as a bare number, or a translation layer omitted the unit.
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 : too many decimals
- unmarshal error
- decode error
- delegating_resolver: failed to determine proxy URL for…
- delegating_resolver: unable to build the resolver for target
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/ac5173c48b2bba16.
Report an issue: GitHub.
Appendix: source
Thrown at internal/serviceconfig/duration.go:67
}
// Generated output always contains 0, 3, 6, or 9 fractional digits,
// depending on required precision.
str := fmt.Sprintf("%s%d.%09d", sign, sec, ns)
str = strings.TrimSuffix(str, "000")
str = strings.TrimSuffix(str, "000")
str = strings.TrimSuffix(str, ".000")
return []byte(fmt.Sprintf("\"%ss\"", str)), nil
}
// 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)View on GitHub (pinned to 0c51461d27)