apache/beam · error

structural DoFn passed by value, ensure that the…

Error message

structural DoFn passed by value, ensure that the ProcessElement method has a value receiver or pass the DoFn by pointer

What it means

A secondary hint wrapped onto the "failed to find ProcessElement method" error when fn.Recv is non-nil and is not a pointer. It tells the developer that Go reflection on a value receiver cannot surface pointer-receiver methods.

Solutions

  1. Pass the DoFn by pointer (add &)
  2. Or switch ProcessElement to a value receiver so it is in the value's method set
  3. Store DoFn instances as pointer-typed variables to avoid recurrence

Example fix

// before
func (m MyFn) is fine — but if receiver is *MyFn: beam.ParDo(s, m, in)
// after
beam.ParDo(s, &m, in)
Defensive patterns

Strategy: validation

Validate before calling

if reflect.TypeOf(fn).Kind() == reflect.Ptr { _, ok := reflect.TypeOf(fn).MethodByName("ProcessElement"); if !ok { return errors.New("ProcessElement not reachable") } }

Type guard

func isStructPointer(fn any) bool {
  v := reflect.ValueOf(fn)
  return v.IsValid() && v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct
}

Prevention

When it happens

Trigger: AsDoFn on a struct value (not pointer) where ProcessElement was declared with a pointer receiver, so the method set of the value does not include it.

Common situations: Copy-pasting a DoFn and forgetting the & when passing it to beam.ParDo; passing DoFn values stored in variables of value type.

Related errors


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

Appendix: source

Thrown at sdks/go/pkg/beam/core/graph/fn.go:507

// validation is done by best effort and may miss some edge cases.
func AsDoFn(fn *Fn, numMainIn mainInputs) (*DoFn, error) {
	addContext := func(err error, fn *Fn) error {
		return errors.WithContextf(err, "graph.AsDoFn: for Fn named %v", fn.Name())
	}

	if fn.methods == nil {
		fn.methods = make(map[string]*funcx.Fn)
	}
	if fn.Fn != nil {
		fn.methods[processElementName] = fn.Fn
	}

	if _, ok := fn.methods[processElementName]; !ok {
		err := errors.Errorf("failed to find %v method", processElementName)
		if fn.Recv != nil {
			v := reflect.ValueOf(fn.Recv)
			if v.Kind() != reflect.Ptr {
				err = errors.Wrap(err, "structural DoFn passed by value, ensure that the ProcessElement method has a value receiver or pass the DoFn by pointer")
			}
		}
		return nil, addContext(err, fn)
	}

	// Make sure that all state entries have keys. If they don't set them to the struct field name.
	if fn.Recv != nil {
		v := reflect.Indirect(reflect.ValueOf(fn.Recv))
		for i := 0; i < v.NumField(); i++ {
			f := v.Field(i)
			if f.CanInterface() {
				if ps, ok := f.Interface().(state.PipelineState); ok {
					if ps.StateKey() == "" {
						f.FieldByName("Key").SetString(v.Type().Field(i).Name)
					}
				}
			}
		}

View on GitHub (pinned to 12126d8942)