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
- Register the dynamic DoFn's type with beam.RegisterType before encoding the pipeline
- Ensure the generator function is a named package-level function whose type is resolvable
- Check encodeType on the failing type in isolation to see the underlying reason
- 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
- Register dynamic DoFn types in init()
- Use named generators in stable packages
- Round-trip encode/decode tests for pipelines with DynFns
- Avoid anonymous/internal types in generated DoFns
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)