apache/beam · error
bad output type
Error message
bad output type
What it means
EncodeMultiEdge wraps encodeFullType failures for an output as 'bad output type' (context 'encoding userfn'). The output PCollection's FullType could not be encoded into MultiEdge_Outbound, aborting graph serialization.
Solutions
- Read the inner encodeFullType error for the concrete type problem
- Change the output element type to a supported encodable Go type
- Register custom types used in outputs
- Verify beam dependency versions are consistent
Example fix
// before
out := beam.SideInput... typed via reflect.TypeOf(func(){}) // unencodable
// after
out typed as a struct or primitive type supported by encodeFullType Defensive patterns
Strategy: validation
Validate before calling
for _, out := range edge.Output {
if !isEncodableGoType(out.Type.Type()) {
return fmt.Errorf("output type %s not encodable", out.Type.Type())
}
} Type guard
func isEncodableGoType(t reflect.Type) bool {
switch t.Kind() {
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
return false
}
return true
} Try / catch
t, err := encodeFullType(out.Type)
if err != nil {
return fmt.Errorf("output type %v not encodable: %w", out.Type.Type(), err)
} Prevention
- Emit outputs of supported element types from DoFns
- Register custom output structs
- Avoid func/chan-typed outputs
- Validate graph encoding before remote submission
When it happens
Trigger: EncodeMultiEdge iterating edge.Output where encodeFullType(out.Type) fails — output typed with functions, channels, or otherwise unrepresentable Go types.
Common situations: Custom DoFn outputs using unsupported Go types; constructing graph edges by hand; mismatched beam versions producing types the encoder can't handle.
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/cf60f2d2f7c069d8.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/go/pkg/beam/core/runtime/graphx/serialize.go:82
}
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)
}
ret.Inbound = append(ret.Inbound, &v1pb.MultiEdge_Inbound{Kind: kind, Type: t})
}
for _, out := range edge.Output {
t, err := encodeFullType(out.Type)
if err != nil {
wrapped := errors.Wrap(err, "bad output type")
return nil, errors.WithContextf(wrapped, "encoding userfn %v", edge)
}
ret.Outbound = append(ret.Outbound, &v1pb.MultiEdge_Outbound{Type: t})
}
return ret, nil
}
// DecodeMultiEdge converts the wire representation into the preprocessed
// components representing that edge. We deserialize to components to avoid
// inserting the edge into a graph or creating a detached edge.
func DecodeMultiEdge(edge *v1pb.MultiEdge) (graph.Opcode, *graph.Fn, *window.Fn, []*graph.Inbound, []*graph.Outbound, error) {
var u *graph.Fn
var wfn *window.Fn
var inbound []*graph.Inbound
var outbound []*graph.Outbound
opcode := graph.Opcode(edge.Opcode)
View on GitHub (pinned to 12126d8942)