apache/beam · error

receiver type %v must be registered

Error message

receiver type %v must be registered

What it means

Thrown by encodeFn in Beam Go's graphx serializer when encoding a structural DoFn whose receiver type has no runtime.TypeKey or is not present in the runtime type registry (LookupType fails). Beam requires all serialized types to be pre-registered so the pipeline can be decoded on a worker; an unregistered receiver type would produce a graph the remote process cannot reconstruct.

Source

Thrown at sdks/go/pkg/beam/core/runtime/graphx/serialize.go:248

		}}, nil

	case u.Fn != nil:
		fn, err := encodeUserFn(u.Fn)
		if err != nil {
			wrapped := errors.Wrap(err, "bad userfn")
			return nil, errors.WithContextf(wrapped, "encoding DoFn %v", u)
		}
		return &v1pb.Fn{Fn: fn}, nil

	case u.Recv != nil:
		t := reflect.TypeOf(u.Recv)
		k, ok := runtime.TypeKey(reflectx.SkipPtr(t))
		if !ok {
			err := errors.Errorf("failed to create TypeKey for receiver type %T", u.Recv)
			return nil, errors.WithContextf(err, "encoding structural DoFn %v", u)
		}
		if _, ok := runtime.LookupType(k); !ok {
			err := errors.Errorf("receiver type %v must be registered", t)
			return nil, errors.WithContextf(err, "encoding structural DoFn %v", u)
		}
		typ, err := encodeType(t)
		if err != nil {
			wrapped := errors.Wrapf(err, "failed to encode receiver type %T", u.Recv)
			return nil, errors.WithContextf(wrapped, "encoding structural DoFn %v", u)
		}

		data, err := jsonx.Marshal(u.Recv)
		if err != nil {
			wrapped := errors.Wrapf(err, "failed to marshal receiver %v", u.Recv)
			return nil, errors.WithContextf(wrapped, "encoding structural DoFn %v", u)
		}
		return &v1pb.Fn{Type: typ, Opt: string(data)}, nil

	default:
		return nil, errors.Errorf("failed to encode DoFn %v, missing fn", u)
	}

View on GitHub (pinned to 12126d8942)

Solutions

  1. Register the receiver type at package init with runtime.RegisterType(reflect.TypeOf(MyFn{})) (or call beam.RegisterFunction/registerDoFn helpers).
  2. Ensure the package containing the registration is imported by both the driver and the worker binary.
  3. Verify with runtime.TypeKey(reflectx.SkipPtr(reflect.TypeOf(fn))) and runtime.LookupType that the type resolves before building the pipeline.
  4. If the DoFn is genuinely dynamic/anonymous, switch to a named registered type or an emulated/serialized DoFn form.

Example fix

// before
func init() { /* no registration */ }
type myFn struct{}

// after
type myFn struct{}
func init() { runtime.RegisterType(reflect.TypeOf(myFn{})) }
Defensive patterns

Strategy: validation

Validate before calling

k, ok := runtime.TypeKey(reflectx.SkipPtr(reflect.TypeOf(myFn{})))
if !ok || func() bool { _, ok := runtime.LookupType(k); return !ok }() {
    runtime.RegisterType(reflect.TypeOf(myFn{}))
}

Type guard

func typeRegistered(t reflect.Type) bool {
    k, ok := runtime.TypeKey(reflectx.SkipPtr(t))
    if !ok { return false }
    _, found := runtime.LookupType(k)
    return found
}

Prevention

When it happens

Trigger: Calling EncodeMultiEdge on a transform whose DoFn is a structural/custom fn whose receiver type was never registered via runtime.RegisterType (or the beam package's init-time registration).

Common situations: Using a locally-defined or anonymous struct DoFn in a custom transform; registering types in a package the worker binary does not import; renaming/moving a type so registry init no longer runs.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/8471c11164ec7f00. Report an issue: GitHub.