go-kratos/kratos · warning
field already set for oneof %q
Error message
field already set for oneof %q
What it means
Form-binding decoder error raised after all form values for a path were collected: the target singular field belongs to a protobuf oneof, and a different member of that same oneof was already set earlier in the request. Because a oneof can hold only one value, populating a second member is rejected instead of silently overwriting.
Source
Thrown at encoding/form/proto_decode.go:69
if fd.IsMap() && len(fieldPath) == 2 {
return populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values)
}
if i == len(fieldPath)-1 {
break
}
if fd.Message() == nil || fd.Cardinality() == protoreflect.Repeated {
if fd.IsMap() && len(fieldPath) > 1 {
// post subfield
return populateMapField(fd, v.Mutable(fd).Map(), []string{fieldPath[1]}, values)
}
return fmt.Errorf("invalid path: %q is not a message", fieldName)
}
v = v.Mutable(fd).Message()
}
if of := fd.ContainingOneof(); of != nil {
if f := v.WhichOneof(of); f != nil {
return fmt.Errorf("field already set for oneof %q", of.FullName().Name())
}
}
switch {
case fd.IsList():
return populateRepeatedField(fd, v.Mutable(fd).List(), values)
case fd.IsMap():
return populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values)
}
if len(values) > 1 {
return fmt.Errorf("too many values for field %q: %s", fd.FullName().Name(), strings.Join(values, ", "))
}
return populateField(fd, v, values[0])
}
func getFieldDescriptor(v protoreflect.Message, fieldName string) protoreflect.FieldDescriptor {
var (
fields = v.Descriptor().Fields()
fd = getDescriptorByFieldAndName(fields, fieldName)View on GitHub (pinned to 668db92c2c)
Solutions
- Send only one member of each oneof per request; omit the other key entirely (not empty string, which still counts for some kinds)
- On the client, build the query by skipping zero-value oneof members before serializing
- If both values are legitimately needed at once, change the proto: split them into independent fields instead of a oneof
- If you own the server and want last-write-wins semantics, decode into a custom struct or pre-sanitize the url.Values to keep only the last member per oneof before form binding
Example fix
// proto: oneof by { string name = 1; uint64 id = 2; }
// before: GET /users?by.name=ken&by.id=7 -> field already set for oneof "by"
// after: GET /users?by.id=7 Defensive patterns
Strategy: validation
Validate before calling
// Keep only one member per oneof before binding
func dedupeOneofs(v url.Values, msg protoreflect.Message) url.Values {
out := url.Values{}
seen := map[protoreflect.FullName]string{}
for _, key := range sortedKeys(v) { // deterministic order: first wins
fd := lookupField(msg, key)
if fd != nil {
if of := fd.ContainingOneof(); of != nil {
if prev, dup := seen[of.FullName()]; dup {
_ = prev
continue
}
seen[of.FullName()] = key
}
}
out[key] = v[key]
}
return out
} Type guard
func oneofMembers(msg protoreflect.Message) map[string]string {
// map query-key -> oneof full name, used client-side to avoid sending two
members := map[string]string{}
msg.Descriptor().Fields().Range(func(fd protoreflect.FieldDescriptor) bool {
if of := fd.ContainingOneof(); of != nil {
members[string(fd.Name())] = string(of.FullName())
}
return true
})
return members
} Try / catch
if err := binding.BindQuery(msg, q); err != nil {
if strings.Contains(err.Error(), "field already set for oneof") {
return errors.BadRequest("ONEOF_CONFLICT", "send only one of the mutually exclusive fields: "+err.Error())
}
} Prevention
- Client: omit unset oneof members entirely instead of sending empty strings
- Document oneof fields as mutually exclusive in the API surface (OpenAPI oneOf)
- Build query params from the set oneof member only (switch on msg.WhichOneof)
- Reject duplicate oneof keys at the gateway before they reach the form codec
When it happens
Trigger: A query/form request that supplies two parameters mapping to different members of the same oneof, e.g. proto `oneof filter { string name = 1; int32 id = 2; }` with query `?filter.name=x&filter.id=5` (or flat `?name=x&id=5` depending on descriptor lookup). Both keys resolve, the first sets the oneof, the second triggers WhichOneof != nil and this error.
Common situations: Frontend forms that always send all optional filter fields; query-string builders that serialize empty defaults instead of omitting unset oneof members; API consumers unaware that mutually-exclusive fields share a oneof after a proto refactor.
Related errors
- invalid path: %q is not a message
- too many values for field %q: %s
- parsing field %q: %w
- parsing list %q: %w
- parsing map key %q: %w
AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16).
Data as JSON: /api/errors/ae9f161bd90087e9.
Report an issue: GitHub.