apache/beam · error

bad function type

Error message

bad function type

What it means

encodeFn serializes a graph.Fn. For a dynamically generated DoFn (u.DynFn), it must serialize the generator's type via encodeType(u.DynFn.T); if that type cannot be encoded (unregistered or unresolvable type), the error is wrapped as "bad function type". DynFn types must be registered in the runtime type registry.

Solutions

  1. Register the dynamic DoFn's type with beam.RegisterType before encoding the pipeline
  2. Ensure the generator function is a named package-level function whose type is resolvable
  3. Check encodeType on the failing type in isolation to see the underlying reason
  4. Avoid anonymous types / anonymous packages in generated DoFn code

Example fix

// before: dyn fn type never registered
// after
func init() {
	beam.RegisterType(reflect.TypeOf((*myDynFn)(nil)).Elem())
}
Defensive patterns

Strategy: validation

Validate before calling

t := reflect.TypeOf(dynFnType)
if _, ok := runtime.LookupType(runtime.TypeKey(reflectx.SkipPtr(t))); !ok {
	return fmt.Errorf("register type %v before encoding pipeline", t)
}

Type guard

func isRegistered(t reflect.Type) bool {
	_, ok := runtime.LookupType(runtime.TypeKey(reflectx.SkipPtr(t)))
	return ok
}

Try / catch

mb, err := graphx.Marshal(model)
if err != nil && strings.Contains(err.Error(), "bad function type") {
	return fmt.Errorf("dyn DoFn type unregistered: %w", err)
}

Prevention

When it happens

Trigger: encodeFn (called from EncodeMultiEdge) encounters a DynFn whose generator function type u.DynFn.T is not registered/encodable — e.g. the DoFn was generated in code whose type wasn't registered with the type registry.

Common situations: Dynamic DoFns created by code generators in packages that never call beam.RegisterType; types from anonymous or internal packages; model serialization across different binaries with differing registries.

Related errors


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

Appendix: source

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

	return d.Nanoseconds() / 1e6
}

func ms2duration(d int64) time.Duration {
	return time.Duration(d) * time.Millisecond
}

// encodeFn encodes a graph.Fn into a v1pb.Fn proto message.
// All string fields in the DoFn struct must be UTF-8 compliant. The vet runner
// (--beam_strict) will detect any non-UTF8 strings that would fail during JSON serialization.
// The check will be skipped for subtypes that implement the MarshalJSON and
// UnmarshalJSON interface methods.
func encodeFn(u *graph.Fn) (*v1pb.Fn, error) {
	switch {
	case u.DynFn != nil:
		gen := reflectx.FunctionName(u.DynFn.Gen)
		t, err := encodeType(u.DynFn.T)
		if err != nil {
			wrapped := errors.Wrap(err, "bad function type")
			return nil, errors.WithContextf(wrapped, "encoding dynamic DoFn %v", u)
		}
		return &v1pb.Fn{Dynfn: &v1pb.DynFn{
			Name: u.DynFn.Name,
			Type: t,
			Data: u.DynFn.Data,
			Gen:  gen,
		}}, 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:

View on GitHub (pinned to 12126d8942)