apache/beam · error
bad combinefn
Error message
bad combinefn
What it means
EncodeMultiEdge wraps encodeFn failures for a CombineFn as 'bad combinefn' (also context-tagged 'encoding userfn'). Like DoFns, CombineFns must be serializable named types with resolvable references. The error indicates the graph encoder could not turn the CombineFn into its protobuf representation.
Solutions
- Check the wrapped inner error from encodeFn for the root cause
- Define the CombineFn as a named exported top-level type
- Register accumulator/result types used by the CombineFn
- Re-run pipeline construction after fixing the fn definition
Example fix
// before
beam.Combine(s, func(a, b int) int { return a + b }, in) // closure
// after
type sumFn struct{}
func (sumFn) CreateAccumulator() int { return 0 }
func (sumFn) AddInput(a, b int) int { return a + b }
func (sumFn) MergeAccumulators(a, b int) int { return a + b }
beam.Combine(s, sumFn{}, in) Defensive patterns
Strategy: validation
Validate before calling
if !isSerializableCombineFn(edge.CombineFn) {
return errors.New("CombineFn must be a named exported type with registered accumulator types")
} Type guard
func isSerializableCombineFn(fn interface{}) bool {
t := reflect.TypeOf(fn)
return t != nil && t.Name() != "" && t.PkgPath() != ""
} Try / catch
ref, err := encodeFn((*graph.Fn)(edge.CombineFn))
if err != nil {
return fmt.Errorf("CombineFn %T not serializable: %w", edge.CombineFn, err)
} Prevention
- Define CombineFns as named top-level types
- Register accumulator/result types for remote marshaling
- Avoid inline function literals passed to beam.Combine
- Smoke-test graph encoding before submitting to a runner
When it happens
Trigger: EncodeMultiEdge on an edge with edge.CombineFn != nil where encodeFn fails — closures, anonymous structs, unregistered custom accumulator types, or encode-time type resolution failures.
Common situations: beam.Combine with an inline/anonymous combine function; custom accumulator types lacking registration when shipping to a remote runner; refactoring that breaks recorded function paths.
Understand the failure class
Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.
Related errors
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/5c539662ff042583.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/go/pkg/beam/core/runtime/graphx/serialize.go:57
// EncodeMultiEdge converts the preprocessed representation into the wire
// representation of the multiedge, capturing input and output type information.
func EncodeMultiEdge(edge *graph.MultiEdge) (*v1pb.MultiEdge, error) {
ret := &v1pb.MultiEdge{}
ret.Opcode = string(edge.Op)
if edge.DoFn != nil {
ref, err := encodeFn((*graph.Fn)(edge.DoFn))
if err != nil {
wrapped := errors.Wrap(err, "bad userfn")
return nil, errors.WithContextf(wrapped, "encoding userfn %v", edge)
}
ret.Fn = ref
}
if edge.CombineFn != nil {
ref, err := encodeFn((*graph.Fn)(edge.CombineFn))
if err != nil {
wrapped := errors.Wrap(err, "bad combinefn")
return nil, errors.WithContextf(wrapped, "encoding userfn %v", edge)
}
ret.Fn = ref
}
if edge.WindowFn != nil {
ret.WindowFn = encodeWindowFn(edge.WindowFn)
}
for _, in := range edge.Input {
kind, err := encodeInputKind(in.Kind)
if err != nil {
wrapped := errors.Wrap(err, "bad input type")
return nil, errors.WithContextf(wrapped, "encoding userfn %v", edge)
}
t, err := encodeFullType(in.Type)
if err != nil {
wrapped := errors.Wrap(err, "bad input type")
return nil, errors.WithContextf(wrapped, "encoding userfn %v", edge)View on GitHub (pinned to 12126d8942)