apache/beam · error

interface type must have methods

Error message

interface type %v must have methods

What it means

RowEncoderBuilder.Register panics when rt is the empty interface (interface{}) with no methods. An empty interface has no schema or concrete representation, so the SDK cannot attach a custom row encoder to it and rejects the registration.

Solutions

  1. Register the concrete struct type (e.g. reflect.TypeOf(Foo{})) instead of interface{}
  2. Resolve the concrete type with a type switch or typed generic parameter before calling Register

Example fix

// before
b.Register(reflect.TypeOf((*any)(nil)).Elem(), encFn)
// after
b.Register(reflect.TypeOf(Foo{}), encFn)
Defensive patterns

Strategy: validation

Validate before calling

if rt.Kind() == reflect.Interface && rt.NumMethod() == 0 {
	return fmt.Errorf("cannot register encoder for empty interface %v", rt)
}

Type guard

func isRegisterableType(rt reflect.Type) bool {
	return rt != nil && !(rt.Kind() == reflect.Interface && rt.NumMethod() == 0)
}

Try / catch

func safeRegister(b *rowcoder.RowEncoderBuilder, rt reflect.Type, f any) (err error) {
	defer func() { if r := recover(); r != nil { err = fmt.Errorf("register: %v", r) } }()
	b.Register(rt, f)
	return nil
}

Prevention

When it happens

Trigger: Calling b.Register(reflect.TypeOf((*any)(nil)).Elem(), encFn) or passing any zero-method interface type as rt while registering row encoders.

Common situations: Generic pipeline helpers that thread element types as interface{} and pass them straight through to Register without resolving to the concrete type.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at sdks/go/pkg/beam/core/graph/coder/row_encoder.go:55

type encoderProvider = func(reflect.Type) (func(any, io.Writer) error, error)

// Register accepts a provider for the given type to schema encode values of that type.
//
// When generating encoding functions, this builder will first check for exact type
// matches, then against interfaces with registered factories in recency order of
// registration, and then finally use the default Beam Schema encoding behavior.
//
// TODO(BEAM-9615): Add final factory types. This interface is subject to change.
// Currently f must be a function of the type func(reflect.Type) func(T, io.Writer) (error).
func (b *RowEncoderBuilder) Register(rt reflect.Type, f any) {
	fe, ok := f.(encoderProvider)
	if !ok {
		panic(fmt.Sprintf("%T isn't a supported encoder function type (passed with %v)", f, rt))
	}

	if rt.Kind() == reflect.Interface && rt.NumMethod() == 0 {
		panic(fmt.Sprintf("interface type %v must have methods", rt))
	}
	if b.allFuncs == nil {
		b.allFuncs = make(map[reflect.Type]encoderProvider)
	}
	b.allFuncs[rt] = fe
	if rt.Kind() == reflect.Interface {
		b.ifaceFuncs = append(b.ifaceFuncs, rt)
	}
}

// Build constructs a Beam Schema coder for the given type, using any providers registered for
// itself or it's fields.
func (b *RowEncoderBuilder) Build(rt reflect.Type) (func(any, io.Writer) error, error) {
	if err := rowTypeValidation(rt, true); err != nil {
		return nil, err
	}
	return b.encoderForType(rt)
}

View on GitHub (pinned to 12126d8942)