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

  1. Read the inner encodeFullType error for the concrete type problem
  2. Change the output element type to a supported encodable Go type
  3. Register custom types used in outputs
  4. 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

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)