apache/beam · error

bad decoding function

Error message

bad decoding function

What it means

encodeCustomCoder calls encodeUserFn on the decoding function c.Dec; if that function cannot be resolved to a serializable named function reference, the error is wrapped as "bad decoding function". Like its encoding counterpart, Beam can only serialize coders whose functions are named, top-level, addressable functions.

Source

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

		outbound = append(outbound, &graph.Outbound{Type: t})
	}

	return opcode, u, wfn, inbound, outbound, nil
}

func encodeCustomCoder(c *coder.CustomCoder) (*v1pb.CustomCoder, error) {
	t, err := encodeType(c.Type)
	if err != nil {
		return nil, errors.WithContextf(err, "encoding custom coder %v for type %v", c, c.Type)
	}
	enc, err := encodeUserFn(c.Enc)
	if err != nil {
		wrapped := errors.Wrap(err, "bad encoding function")
		return nil, errors.WithContextf(wrapped, "encoding custom coder %v", c)
	}
	dec, err := encodeUserFn(c.Dec)
	if err != nil {
		wrapped := errors.Wrap(err, "bad decoding function")
		return nil, errors.WithContextf(wrapped, "encoding custom coder %v", c)
	}

	ret := &v1pb.CustomCoder{
		Name: c.Name,
		Type: t,
		Enc:  enc,
		Dec:  dec,
	}
	return ret, nil
}

func decodeCustomCoder(c *v1pb.CustomCoder) (*coder.CustomCoder, error) {
	t, err := decodeType(c.Type)
	if err != nil {
		return nil, errors.WithContextf(err, "decoding custom coder %v for type %v", c, c.Type)
	}
	enc, err := decodeUserFn(c.Enc)

View on GitHub (pinned to 12126d8942)

Solutions

  1. Make the decode function a named package-level function
  2. Replace bound method values with package-level wrapper functions
  3. Ensure the package defining the decoder is importable by the decoding worker
  4. Test the coder round-trip locally with EncodeCoderRef/DecodeCoderRef

Example fix

// before
dec := func(b []byte) (T, error) { ... }
c, _ := coder.NewCustomCoder("myCoder", typ, enc, dec)
// after
func decodeT(b []byte) (T, error) { ... }
c, _ := coder.NewCustomCoder("myCoder", typ, encodeT, decodeT)
Defensive patterns

Strategy: type-guard

Validate before calling

func isNamedFunc(f interface{}) bool {
	name := reflectx.FunctionName(f)
	return name != "" && !strings.Contains(name, "func") && !strings.Contains(name, "-")
}
// validate: isNamedCoder(c.Dec)

Type guard

func isNamedFunc(f interface{}) bool {
	name := reflectx.FunctionName(f)
	return name != "" && !strings.Contains(name, "func") && !strings.Contains(name, "-")
}

Try / catch

ref, err := graphx.EncodeCoderRef(c)
if err != nil && strings.Contains(err.Error(), "bad decoding function") {
	return fmt.Errorf("coder %v uses non-serializable dec fn: %w", c, err)
}

Prevention

When it happens

Trigger: coder.NewCustomCoder created with an anonymous or closure decode function (or a bound method), then serialized through Add or EncodeCoderRef — encodeUserFn(c.Dec) fails.

Common situations: Inline func literals as decode callbacks; decoder functions defined inside main or tests where the decoder binary can't resolve them; refactor moved the function so the recorded name no longer resolves.

Related errors


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